Validator
import { Badge } from '../../../../../components/ui/badge';
Validator
概要
Validatorは、Swift向けのシンプルで柔軟なバリデーションライブラリです。iOS、macOS、tvOS、watchOSアプリケーションでユーザー入力の検証を簡単に実装できます。宣言的なAPIと豊富な組み込みバリデーションルールを提供し、カスタムバリデーションの作成も容易です。
主な特徴
- 宣言的なAPI - 直感的で読みやすいバリデーションルールの定義
- 豊富な組み込みルール - よく使用されるバリデーションパターンを事前定義
- カスタムバリデーション - 独自のバリデーションロジックを簡単に追加
- エラーメッセージ - カスタマイズ可能なエラーメッセージ
- チェーン可能 - 複数のバリデーションルールを組み合わせ可能
- 型安全 - Swiftの型システムを活用した安全なバリデーション
インストール
Swift Package Manager
dependencies: [
.package(url: "https://github.com/adamwaite/Validator.git", from: "3.2.1")
]
CocoaPods
pod 'Validator'
Carthage
github "adamwaite/Validator"
基本的な使い方
シンプルなバリデーション
import Validator
// メールアドレスのバリデーション
let emailRule = ValidationRulePattern(
pattern: EmailValidationPattern(),
error: ValidationError(message: "無効なメールアドレスです")
)
let result = "[email protected]".validate(rule: emailRule)
switch result {
case .valid:
print("有効なメールアドレスです")
case .invalid(let errors):
errors.forEach { print($0.message) }
}
テキストフィールドのバリデーション
class LoginViewController: UIViewController {
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
func setupValidation() {
// メールアドレスのバリデーションルール
emailTextField.validationRules = ValidationRuleSet<String>()
emailTextField.validationRules?.add(rule: ValidationRuleRequired<String>(
error: ValidationError(message: "メールアドレスは必須です")
))
emailTextField.validationRules?.add(rule: ValidationRulePattern(
pattern: EmailValidationPattern(),
error: ValidationError(message: "有効なメールアドレスを入力してください")
))
// パスワードのバリデーションルール
passwordTextField.validationRules = ValidationRuleSet<String>()
passwordTextField.validationRules?.add(rule: ValidationRuleLength(
min: 8,
max: 20,
error: ValidationError(message: "パスワードは8〜20文字で入力してください")
))
}
func validateForm() -> Bool {
let emailResult = emailTextField.validate()
let passwordResult = passwordTextField.validate()
return emailResult.isValid && passwordResult.isValid
}
}
組み込みバリデーションルール
必須チェック
let requiredRule = ValidationRuleRequired<String>(
error: ValidationError(message: "このフィールドは必須です")
)
文字数制限
// 最小文字数
let minLengthRule = ValidationRuleLength(
min: 3,
error: ValidationError(message: "3文字以上入力してください")
)
// 最大文字数
let maxLengthRule = ValidationRuleLength(
max: 100,
error: ValidationError(message: "100文字以内で入力してください")
)
// 範囲指定
let rangeLengthRule = ValidationRuleLength(
min: 3,
max: 20,
error: ValidationError(message: "3〜20文字で入力してください")
)
パターンマッチング
// メールアドレス
let emailRule = ValidationRulePattern(
pattern: EmailValidationPattern(),
error: ValidationError(message: "無効なメールアドレスです")
)
// アルファベットのみ
let alphaRule = ValidationRulePattern(
pattern: AlphaValidationPattern(),
error: ValidationError(message: "アルファベットのみ入力してください")
)
// 数字のみ
let numericRule = ValidationRulePattern(
pattern: NumericValidationPattern(),
error: ValidationError(message: "数字のみ入力してください")
)
// アルファベットと数字
let alphaNumericRule = ValidationRulePattern(
pattern: AlphaNumericValidationPattern(),
error: ValidationError(message: "英数字のみ入力してください")
)
数値の範囲
let ageRule = ValidationRuleComparison<Int>(
min: 18,
max: 100,
error: ValidationError(message: "年齢は18〜100歳の範囲で入力してください")
)
カスタムバリデーション
カスタムパターン
// 日本の郵便番号パターン
struct JapanesePostalCodePattern: ValidationPattern {
func evaluate(_ input: String) -> Bool {
let regex = "^\\d{3}-\\d{4}$"
return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: input)
}
}
let postalCodeRule = ValidationRulePattern(
pattern: JapanesePostalCodePattern(),
error: ValidationError(message: "郵便番号は123-4567の形式で入力してください")
)
カスタムルール
// パスワード強度チェック
struct PasswordStrengthRule: ValidationRule {
typealias InputType = String
var error: ValidationError
func evaluate(with input: String?) -> Bool {
guard let password = input else { return false }
// 大文字、小文字、数字、特殊文字を含むかチェック
let hasUpperCase = password.contains { $0.isUppercase }
let hasLowerCase = password.contains { $0.isLowercase }
let hasNumber = password.contains { $0.isNumber }
let hasSpecialChar = password.contains { !$0.isLetterOrNumber }
return hasUpperCase && hasLowerCase && hasNumber && hasSpecialChar
}
}
let passwordStrengthRule = PasswordStrengthRule(
error: ValidationError(message: "パスワードは大文字、小文字、数字、特殊文字を含む必要があります")
)
条件付きバリデーション
// 他のフィールドの値に基づくバリデーション
class ConditionalValidationRule: ValidationRule {
typealias InputType = String
var error: ValidationError
let condition: () -> Bool
let rule: ValidationRule
init(condition: @escaping () -> Bool, rule: ValidationRule, error: ValidationError) {
self.condition = condition
self.rule = rule
self.error = error
}
func evaluate(with input: String?) -> Bool {
if condition() {
return rule.evaluate(with: input)
}
return true
}
}
複数ルールの組み合わせ
ValidationRuleSet
// ユーザー名のバリデーションセット
let usernameRules = ValidationRuleSet<String>()
// 必須チェック
usernameRules.add(rule: ValidationRuleRequired<String>(
error: ValidationError(message: "ユーザー名は必須です")
))
// 文字数制限
usernameRules.add(rule: ValidationRuleLength(
min: 3,
max: 20,
error: ValidationError(message: "ユーザー名は3〜20文字で入力してください")
))
// 使用可能文字
usernameRules.add(rule: ValidationRulePattern(
pattern: AlphaNumericValidationPattern(),
error: ValidationError(message: "ユーザー名は英数字のみ使用できます")
))
// バリデーション実行
let result = "username123".validate(rules: usernameRules)
エラーハンドリング
カスタムエラーメッセージ
struct LocalizedValidationError: ValidationError {
let key: String
var message: String {
return NSLocalizedString(key, comment: "")
}
}
let emailRule = ValidationRulePattern(
pattern: EmailValidationPattern(),
error: LocalizedValidationError(key: "error.invalid_email")
)
エラー表示
extension UITextField {
func showValidationErrors(_ errors: [ValidationError]) {
// エラーメッセージを表示
let errorMessage = errors.map { $0.message }.joined(separator: "\n")
// エラーラベルに表示
errorLabel.text = errorMessage
errorLabel.isHidden = false
// ボーダーを赤に変更
layer.borderColor = UIColor.red.cgColor
layer.borderWidth = 1.0
}
func clearValidationErrors() {
errorLabel.isHidden = true
layer.borderColor = UIColor.clear.cgColor
layer.borderWidth = 0.0
}
}
フォームバリデーション
フォームバリデーター
class FormValidator {
private var validationRules: [UITextField: ValidationRuleSet<String>] = [:]
func addRule(for textField: UITextField, rules: ValidationRuleSet<String>) {
validationRules[textField] = rules
}
func validate() -> (isValid: Bool, errors: [UITextField: [ValidationError]]) {
var allErrors: [UITextField: [ValidationError]] = [:]
var isValid = true
for (textField, rules) in validationRules {
let result = textField.text?.validate(rules: rules) ?? .invalid([])
switch result {
case .valid:
textField.clearValidationErrors()
case .invalid(let errors):
isValid = false
allErrors[textField] = errors
textField.showValidationErrors(errors)
}
}
return (isValid, allErrors)
}
}
リアルタイムバリデーション
class SignUpViewController: UIViewController {
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var confirmPasswordTextField: UITextField!
private let formValidator = FormValidator()
override func viewDidLoad() {
super.viewDidLoad()
setupValidation()
setupTextFieldObservers()
}
private func setupTextFieldObservers() {
// リアルタイムバリデーション
emailTextField.addTarget(
self,
action: #selector(textFieldDidChange(_:)),
for: .editingChanged
)
passwordTextField.addTarget(
self,
action: #selector(textFieldDidChange(_:)),
for: .editingChanged
)
}
@objc private func textFieldDidChange(_ textField: UITextField) {
// 入力中のバリデーション
if let rules = textField.validationRules {
let result = textField.text?.validate(rules: rules) ?? .invalid([])
switch result {
case .valid:
textField.clearValidationErrors()
case .invalid(let errors):
// 入力中は軽いフィードバックのみ
textField.layer.borderColor = UIColor.orange.cgColor
}
}
}
}
高度な使用例
非同期バリデーション
class AsyncValidationRule: ValidationRule {
typealias InputType = String
var error: ValidationError
private let validationClosure: (String, @escaping (Bool) -> Void) -> Void
init(
error: ValidationError,
validation: @escaping (String, @escaping (Bool) -> Void) -> Void
) {
self.error = error
self.validationClosure = validation
}
func validateAsync(input: String?, completion: @escaping (ValidationResult) -> Void) {
guard let input = input else {
completion(.invalid([error]))
return
}
validationClosure(input) { isValid in
if isValid {
completion(.valid)
} else {
completion(.invalid([self.error]))
}
}
}
}
// 使用例:ユーザー名の重複チェック
let usernameAvailableRule = AsyncValidationRule(
error: ValidationError(message: "このユーザー名は既に使用されています")
) { username, completion in
// APIコール
APIClient.checkUsernameAvailability(username) { isAvailable in
completion(isAvailable)
}
}
バリデーション結果の集約
struct ValidationSummary {
let results: [String: ValidationResult]
var isValid: Bool {
return results.values.allSatisfy { $0.isValid }
}
var errors: [String: [ValidationError]] {
var errorMap: [String: [ValidationError]] = [:]
for (key, result) in results {
switch result {
case .invalid(let errors):
errorMap[key] = errors
default:
break
}
}
return errorMap
}
func errorMessage(for key: String) -> String? {
guard let errors = errors[key] else { return nil }
return errors.map { $0.message }.joined(separator: ", ")
}
}
ベストプラクティス
1. バリデーションルールの再利用
// 共通バリデーションルールの定義
struct ValidationRules {
static let email = ValidationRulePattern(
pattern: EmailValidationPattern(),
error: ValidationError(message: "有効なメールアドレスを入力してください")
)
static let password = ValidationRuleSet<String>()
.addRule(ValidationRuleRequired<String>(
error: ValidationError(message: "パスワードは必須です")
))
.addRule(ValidationRuleLength(
min: 8,
error: ValidationError(message: "パスワードは8文字以上必要です")
))
static let phoneNumber = ValidationRulePattern(
pattern: PhoneNumberValidationPattern(),
error: ValidationError(message: "有効な電話番号を入力してください")
)
}
2. エラーメッセージの国際化
extension ValidationError {
static func localized(_ key: String) -> ValidationError {
return ValidationError(message: NSLocalizedString(key, comment: ""))
}
}
// 使用例
let emailRule = ValidationRulePattern(
pattern: EmailValidationPattern(),
error: .localized("validation.error.invalid_email")
)
3. UIとの統合
protocol ValidatableTextField {
var validationRules: ValidationRuleSet<String>? { get set }
func validate() -> ValidationResult
func showError(_ error: String)
func clearError()
}
extension UITextField: ValidatableTextField {
private struct AssociatedKeys {
static var validationRules = "validationRules"
}
var validationRules: ValidationRuleSet<String>? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.validationRules) as? ValidationRuleSet<String>
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.validationRules, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
func validate() -> ValidationResult {
return text?.validate(rules: validationRules ?? ValidationRuleSet()) ?? .invalid([])
}
}
他のバリデーターとの比較
SwiftValidator vs Validator
| 機能 | Validator | SwiftValidator |
|---|---|---|
| 宣言的API | ✅ | ✅ |
| 組み込みルール | 豊富 | 標準的 |
| カスタムルール | 簡単 | 可能 |
| 非同期バリデーション | 拡張可能 | 組み込み |
| UIとの統合 | 良好 | 良好 |
選択の指針
- Validator: シンプルで柔軟なバリデーションが必要な場合
- SwiftValidator: より包括的な機能が必要な場合
- 手動実装: 特殊な要件がある場合
まとめ
Validatorは、Swiftアプリケーションにおけるユーザー入力の検証を簡単かつ効果的に実装できる優れたライブラリです。豊富な組み込みルール、柔軟なカスタマイズ性、そして優れたUIとの統合により、高品質なフォームバリデーションを実現できます。