5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

iOSアプリのバリデーションについて考えてみた(Swift)

Posted at

Xcode-12 Swift-5.3 iOS-14

はじめに

iOS アプリを作る際たま〜〜〜〜〜にクライアント側でバリデーションしてよ!って言われて実装することがあります。ただ下記のようにするのは嫌なんでなにかしらクラスを分けたくて考えてみました。どなたかいい案があればご教授いただけると幸いです:pray:

ViewController.swift
@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:bangbang:
adamwaite/Validator

必要そうな機能

バリデーションで必要そうな機能は下記かなと思います:thinking:

  • 値の受け取り
    String, Int, Date とか色々型を受けられた方がいいかも?
  • 成功・失敗の返却
  • 複数条件の設定
  • エラーメッセージの返却
    エラーを返すでもいいかも?

実装

実装はほぼこちらの記事を参考にさせていただきました:pray:

【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 を返してみたんですがキャストしないと使えないし微妙かも。。。

実際の動作はこんな感じ。

validation

おわりに

必要そうな機能はとりあえず満たしているんじゃないかなと思います:v:
Android の実装と差がでるの嫌やからバリデーションはサーバーだけでやってくれって思う:innocent:

もっといい方法あるよ!という方がいればご教授いただけると幸いです:bow:

参考

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?