はじめに
TextField等の入力フォームで受け取った文字列をそのままサーバー側に投げるのは、サーバへの負担となってしまうのでなるべく避けたいところです。
そこで明らかに無効な入力があった場合、アプリ側で弾くことができるよう簡易的なバリデーション機能を設けることにしました。
チェック対象
- Password
記事を分かりやすくするため、パスワードのバリデーションのみを考えることにします。
必要な要件
- 英数字のみ
- 大文字小文字の制限は設けない
- 文字数は6~8
よくある簡易的なパスワードという感じです。
実装
上記のようなフォームに入力した場合を想定してつくります。
コード
import Foundation
// ①
enum ValidationResult {
case valid
case dataIsEmpty(section: String)
case lengthInvalid(section: String, min: Int, max: Int)
case invalidFormat(section: String)
// ②
var isValid: Bool {
switch self {
case .valid:
return true
case .dataIsEmpty:
return false
case .lengthInvalid:
return false
case .invalidFormat:
return false
}
}
// ③
var errorMessage: String {
switch self {
case .valid:
return ""
case .dataIsEmpty(let section):
return "\(section)の入力がありません"
case .lengthInvalid(let section, let min, let max):
return "\(section):\(min)文字以上\(max)文字以下で入力してください"
case .invalidFormat(let section):
return "\(section)に使用できない文字が含まれています"
}
}
}
final class Validator {
static let shared: Validator = .init()
private init() {}
// ④
func passwordCheck(with _password: String?, min: Int, max: Int) -> ValidationResult {
// ⑤
guard let _password = _password, !_password.isEmpty else {
return .dataIsEmpty(section: "パスワード")
}
// ⑥
guard _password.count >= min && _password.count <= max else {
return .lengthInvalid(section: "パスワード", min: min, max: max)
}
// ⑦
let pattern = "[^a-zA-Z0-9]"
if _password.range(of: pattern, options: .regularExpression) != nil {
return .invalidFormat(section: "パスワード")
}
// ⑧
return .valid
}
}
解説
① ValidationResult
enum ValidationResult
を定義し、バリデーションの結果をenumで受け取ります。
caseはチェックする内容ごとに用意すると良いです。
- case valid (有効)
- case dataIsEmpty (空入力)
- case lengthInvalid (文字数)
- case invalidFormat (指定外の文字の使用)
今回はこの4つを用意しています。
バリデーションチェックの結果によってこのいずれかを返すことになります。
② isValid: Bool
enumにBool型のisValid
を持たせています。
これは好みによると思いますが、私は呼び出し側でguard
やif
などの条件分岐が書きやすくなるので用意しています。
③ errorMessage: String
enumにString型のerrorMessage
を持たせています。
selfで条件分岐し、それぞれのケースに対応したStringを保持します。
呼び出し側はUIAlertController
にerrorMessage
を渡して表示するなどして使用します。
④ passwordCheck
この関数を外部から呼ぶことになります。
引数で入力された文字列
と文字数の最小値
、文字数の最大値
を受け取ります。
今回はTextField.Textをそのまま受け取る想定ですのでString?型にしています。
処理した戻り値は事前に用意したValidationResult
です。
⑤ 空文字の除外
受け取ったString?がnil
もしくは""
だった場合、ValidationResult.dataIsEmpty
を返します。
その際、(section: "パスワード")とし、エラーメッセージに反映させたい文字列も一緒に渡しています。
(2022.06.30追記)nilチェックだけでなく、!_password.isEmpty
を加えることで、""
といった空文字もここで止めています。
このようにすることで、ユーザー名やメールアドレスといった他のバリデーションを行いたいときに、sectionの文字列を変更するだけで良くなります。
※2022.06.30追記
isEmptyのチェックは不要では?と編集リクエストをいただきました。
コメント欄にて返事をさせていただきました。
⑥ 文字数チェック
受け取った文字列が、決められた文字数の範囲内かどうかを確認します。
範囲外の場合はValidationResult.lengthInvalid
を返します。
ここでも同じようにエラーメッセージに渡したい情報を用意しています。
⑦ 不正な文字を使用していないかの確認
受け取った文字列に、使用できないものが含まれていないかの確認をします。
rangeメソッドを正規表現で使用し、nilで無かった場合(不正な文字が検出された場合)、
ValidationResult. invalidFormat
を返します。
⑧ 問題ない場合
ValidationResult.valid
を返します。
呼び出し側
コード
以下のような形で呼び出すことができます。
// フォームに入力された文字列と仮定
let textFieldText: String? = "dsna-uh/"
let result = Validator.shared.passwordCheck(with: textFieldText, min: 6, max: 8)
print(result.isValid) // false
print(result.errorMessage) // パスワードに使用できない文字が含まれています
isValidを使用してswitchで条件分岐させるような書き方もできます。
let result = Validator.shared.passwordCheck(with: textFieldText, min: 6, max: 8)
switch result.isValid {
case true:
// 問題なかった場合の処理
case false:
// 問題があった場合の処理
}
終わりに
ここまで読んでいただきありがとうございました。
試行錯誤しながら実装したものなので、良くない部分やもっとスマートな記述方法があったりするかもしれませんが、これからバリデーションを実装しようとしている方の参考になれば幸いです。
上述のコードはPlaygroundで動作しますので、ぜひお試しください。