はじめに
iOS アプリを作る際たま〜〜〜〜〜にクライアント側でバリデーションしてよ!って言われて実装することがあります。ただ下記のようにするのは嫌なんでなにかしらクラスを分けたくて考えてみました。どなたかいい案があればご教授いただけると幸いです
@IBAction private func register(_ sender: Any) {
let value = textField.text!
var message = ""
if value.isEmpty {
message = "空だよ!"
} else if value.count > 7 {
message = "7文字以内にしてよ!"
}
if !message.isEmpty {
showAlert(message: message)
}
}
ライブラリもあるみたいですが archived
adamwaite/Validator
必要そうな機能
バリデーションで必要そうな機能は下記かなと思います
- 値の受け取り
String
,Int
,Date
とか色々型を受けられた方がいいかも? - 成功・失敗の返却
- 複数条件の設定
- エラーメッセージの返却
エラーを返すでもいいかも?
実装
実装はほぼこちらの記事を参考にさせていただきました
【iOS】アプリクライアント側での文字入力のValidation機構について(UITextField, UITextViewなど)
protocol ValidationError: Swift.Error {
}
enum ValidationResult {
case valid
case invalid(ValidationError)
}
extension ValidationResult {
var isValid: Bool {
switch self {
case .valid:
return true
case .invalid:
return false
}
}
}
protocol Validator {
func validate() -> ValidationResult
}
protocol CompositeValidator: Validator {
var validators: [Validator] { get }
}
extension CompositeValidator {
func validate() -> ValidationResult {
guard let result = validators.map({ $0.validate() }).first(where: { !$0.isValid }) else {
return .valid
}
return result
}
}
enum NameValidationError: ValidationError {
case empty
case length
}
extension NameValidationError: LocalizedError {
public var errorDescription: String? {
switch self {
case .empty:
return "名前を入力してください。"
case .length:
return "名前は7文字以内で入力してください。"
}
}
}
struct NameEmptyValidator: Validator {
let name: String
func validate() -> ValidationResult {
if name.isEmpty {
return .invalid(NameValidationError.empty)
}
return .valid
}
}
struct NameLengthValidator: Validator {
let name: String
func validate() -> ValidationResult {
if name.count > 7 {
return .invalid(NameValidationError.length)
}
return .valid
}
}
enum BirthdayValidationError: ValidationError {
case minimum
}
extension BirthdayValidationError: LocalizedError {
public var errorDescription: String? {
switch self {
case .minimum:
return "18歳未満の方はご遠慮ください🙏"
}
}
}
struct BirthdayRangeValidator: Validator {
let birthday: Date
func validate() -> ValidationResult {
let calendar = Calendar.current
let age = calendar.dateComponents([.year], from: birthday, to: Date()).year!
if age < 18 {
return .invalid(BirthdayValidationError.minimum)
}
return .valid
}
}
struct ProfileValidator: CompositeValidator {
let validators: [Validator]
init(name: String, birthday: Date) {
self.validators = [
NameEmptyValidator(name: name),
NameLengthValidator(name: name),
BirthdayRangeValidator(birthday: birthday)
]
}
}
使い方
let validator = ProfileValidator(name: nameTextField.text!,
birthday: dateFormatter.date(from: birthdayTextField.text!)!)
switch validator.validate() {
case .valid:
print("問題なし!")
case .invalid(let error):
print(error.localizedDescription)
}
validate()
の中でごちゃごちゃするの嫌だったので1つずつ分けてみました。
Validator
にプロパティを1つしか持たせていませんがいくらでも追加は可能です!(開始日・終了日とか)
呼び出し側で文言変えられるように Error
を返してみたんですがキャストしないと使えないし微妙かも。。。
実際の動作はこんな感じ。
おわりに
必要そうな機能はとりあえず満たしているんじゃないかなと思います
Android の実装と差がでるの嫌やからバリデーションはサーバーだけでやってくれって思う
もっといい方法あるよ!という方がいればご教授いただけると幸いです