SwiftValidator

ルールベースのSwiftバリデーションライブラリ。UITextField統合で簡単にフォーム検証。

validationformUITextFieldiOSrules

GitHub概要

SwiftValidatorCommunity/SwiftValidator

A rule-based validation library for Swift

スター1,441
ウォッチ39
フォーク373
作成日:2014年11月20日
言語:Swift
ライセンス:MIT License

トピックス

なし

スター履歴

SwiftValidatorCommunity/SwiftValidator Star History
データ取得日時: 2025/10/22 09:50

SwiftValidator

SwiftValidatorは、Swiftで書かれたルールベースのバリデーションライブラリです。UITextFieldとの統合により、iOSアプリケーションのフォーム検証を簡単に実装できます。

特徴

  • ルールベースアーキテクチャ: 複数のバリデーションルールを組み合わせて使用
  • UITextField統合: UITextFieldを直接バリデータに登録
  • カスタマイズ可能: 独自のバリデーションルールを作成可能
  • エラーラベル対応: UILabelを使ったエラーメッセージ表示
  • 順次評価: ルールを左から右に順番に評価
  • 軽量設計: iOSアプリケーションに最適化

インストール

CocoaPods

# Podfile
pod 'SwiftValidator', :git => 'https://github.com/SwiftValidatorCommunity/SwiftValidator.git'

Swift Package Manager

// Package.swift
dependencies: [
    .package(url: "https://github.com/SwiftValidatorCommunity/SwiftValidator.git", from: "4.2.0")
]

Carthage

github "SwiftValidatorCommunity/SwiftValidator"

基本的な使用方法

1. Validatorの初期化とフィールド登録

import SwiftValidator

class ViewController: UIViewController, ValidationDelegate {
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var emailErrorLabel: UILabel!
    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var passwordErrorLabel: UILabel!
    @IBOutlet weak var confirmPasswordTextField: UITextField!
    @IBOutlet weak var confirmPasswordErrorLabel: UILabel!
    
    let validator = Validator()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // フィールドをバリデータに登録
        validator.registerField(emailTextField, 
                               errorLabel: emailErrorLabel, 
                               rules: [RequiredRule(), EmailRule()])
        
        validator.registerField(passwordTextField,
                               errorLabel: passwordErrorLabel,
                               rules: [RequiredRule(), MinLengthRule(length: 8)])
        
        validator.registerField(confirmPasswordTextField,
                               errorLabel: confirmPasswordErrorLabel,
                               rules: [RequiredRule(), ConfirmationRule(confirmField: passwordTextField)])
    }
    
    @IBAction func submitButtonTapped(_ sender: UIButton) {
        validator.validate(self)
    }
}

2. ValidationDelegateの実装

extension ViewController: ValidationDelegate {
    func validationSuccessful() {
        // バリデーション成功時の処理
        print("フォームのバリデーションが成功しました")
        // フォーム送信処理を実装
        submitForm()
    }
    
    func validationFailed(_ errors: [(Validatable, ValidationError)]) {
        // バリデーション失敗時の処理
        for (field, error) in errors {
            print("バリデーションエラー: \(error.errorMessage)")
            // エラーラベルの表示やフィールドのハイライトなど
        }
    }
}

組み込みバリデーションルール

基本ルール

// 必須フィールド
RequiredRule(message: "この項目は必須です")

// 最小文字数
MinLengthRule(length: 8, message: "8文字以上で入力してください")

// 最大文字数
MaxLengthRule(length: 50, message: "50文字以下で入力してください")

// 正確な文字数
ExactLengthRule(length: 10, message: "10文字で入力してください")

専用ルール

// メールアドレス
EmailRule(message: "正しいメールアドレスを入力してください")

// フルネーム
FullNameRule(message: "フルネームを入力してください")

// 郵便番号
ZipCodeRule(message: "正しい郵便番号を入力してください")

// 確認フィールド(パスワード確認など)
ConfirmationRule(confirmField: passwordTextField, 
                message: "パスワードが一致しません")

正規表現ルール

// カスタム正規表現
RegexRule(regex: "^[0-9]+$", message: "数字のみ入力してください")

// 電話番号
RegexRule(regex: "^\\d{3}-\\d{4}-\\d{4}$", 
         message: "XXX-XXXX-XXXX の形式で入力してください")

カスタムバリデーションルール

基本的なカスタムルール

class CustomRule: Rule {
    private var message: String
    
    init(message: String = "カスタムバリデーションエラー") {
        self.message = message
    }
    
    func validate(_ value: String) -> Bool {
        // カスタムバリデーションロジック
        return value.count >= 3 && value.allSatisfy { $0.isLetter }
    }
    
    func errorMessage() -> String {
        return message
    }
}

RegexRuleを継承したカスタムルール

class SSNRule: RegexRule {
    static let regex = "^\\d{3}-\\d{2}-\\d{4}$"
    
    convenience init(message: String = "正しいSSN形式で入力してください") {
        self.init(regex: SSNRule.regex, message: message)
    }
}

class JapanesePhoneRule: RegexRule {
    static let regex = "^(070|080|090)-\\d{4}-\\d{4}$"
    
    convenience init(message: String = "正しい日本の携帯電話番号を入力してください") {
        self.init(regex: JapanesePhoneRule.regex, message: message)
    }
}

高度な使用方法

個別フィールドバリデーション

// 特定のフィールドのみをバリデーション
validator.validateField(emailTextField) { [weak self] error in
    if let error = error {
        self?.handleValidationError(error)
    } else {
        self?.handleValidationSuccess()
    }
}

フィールドの動的登録解除

// フィールドの登録解除
validator.unregisterField(emailTextField)

// 条件に応じて再登録
if shouldValidateEmail {
    validator.registerField(emailTextField, 
                           errorLabel: emailErrorLabel, 
                           rules: [RequiredRule(), EmailRule()])
}

エラーハンドリングとUI更新

func validationFailed(_ errors: [(Validatable, ValidationError)]) {
    // すべてのエラーラベルをクリア
    clearAllErrorLabels()
    
    // エラーを個別に処理
    for (field, error) in errors {
        if let textField = field as? UITextField {
            // テキストフィールドの境界線を赤色に
            textField.layer.borderColor = UIColor.red.cgColor
            textField.layer.borderWidth = 1.0
            
            // エラーラベルに対応
            if let errorLabel = getErrorLabel(for: textField) {
                errorLabel.text = error.errorMessage
                errorLabel.isHidden = false
            }
        }
    }
}

func validationSuccessful() {
    // すべてのエラー表示をクリア
    clearAllErrorLabels()
    
    // 成功時のスタイル適用
    applySuccessStyle()
}

実践的なフォームバリデーション例

ユーザー登録フォーム

class SignUpViewController: UIViewController, ValidationDelegate {
    @IBOutlet weak var usernameTextField: UITextField!
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var confirmPasswordTextField: UITextField!
    @IBOutlet weak var phoneTextField: UITextField!
    @IBOutlet weak var ageTextField: UITextField!
    
    // エラーラベル
    @IBOutlet weak var usernameErrorLabel: UILabel!
    @IBOutlet weak var emailErrorLabel: UILabel!
    @IBOutlet weak var passwordErrorLabel: UILabel!
    @IBOutlet weak var confirmPasswordErrorLabel: UILabel!
    @IBOutlet weak var phoneErrorLabel: UILabel!
    @IBOutlet weak var ageErrorLabel: UILabel!
    
    let validator = Validator()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupValidation()
    }
    
    private func setupValidation() {
        // ユーザー名: 必須、3-20文字、英数字のみ
        validator.registerField(usernameTextField,
                               errorLabel: usernameErrorLabel,
                               rules: [
                                   RequiredRule(message: "ユーザー名は必須です"),
                                   MinLengthRule(length: 3, message: "ユーザー名は3文字以上で入力してください"),
                                   MaxLengthRule(length: 20, message: "ユーザー名は20文字以下で入力してください"),
                                   RegexRule(regex: "^[a-zA-Z0-9]+$", message: "ユーザー名は英数字のみ使用できます")
                               ])
        
        // メールアドレス: 必須、有効なメール形式
        validator.registerField(emailTextField,
                               errorLabel: emailErrorLabel,
                               rules: [
                                   RequiredRule(message: "メールアドレスは必須です"),
                                   EmailRule(message: "正しいメールアドレスを入力してください")
                               ])
        
        // パスワード: 必須、8文字以上、英数字記号混在
        validator.registerField(passwordTextField,
                               errorLabel: passwordErrorLabel,
                               rules: [
                                   RequiredRule(message: "パスワードは必須です"),
                                   MinLengthRule(length: 8, message: "パスワードは8文字以上で入力してください"),
                                   RegexRule(regex: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$",
                                            message: "パスワードは大文字、小文字、数字、記号を含んでください")
                               ])
        
        // パスワード確認: 必須、パスワードと一致
        validator.registerField(confirmPasswordTextField,
                               errorLabel: confirmPasswordErrorLabel,
                               rules: [
                                   RequiredRule(message: "パスワード確認は必須です"),
                                   ConfirmationRule(confirmField: passwordTextField,
                                                   message: "パスワードが一致しません")
                               ])
        
        // 電話番号: 必須、日本の携帯電話番号形式
        validator.registerField(phoneTextField,
                               errorLabel: phoneErrorLabel,
                               rules: [
                                   RequiredRule(message: "電話番号は必須です"),
                                   JapanesePhoneRule(message: "正しい日本の携帯電話番号を入力してください(例: 090-1234-5678)")
                               ])
        
        // 年齢: 必須、18歳以上120歳以下
        validator.registerField(ageTextField,
                               errorLabel: ageErrorLabel,
                               rules: [
                                   RequiredRule(message: "年齢は必須です"),
                                   AgeRule(message: "年齢は18歳以上120歳以下で入力してください")
                               ])
    }
    
    @IBAction func signUpButtonTapped(_ sender: UIButton) {
        validator.validate(self)
    }
    
    // MARK: - ValidationDelegate
    
    func validationSuccessful() {
        // バリデーション成功時の処理
        showSuccessMessage()
        performSignUp()
    }
    
    func validationFailed(_ errors: [(Validatable, ValidationError)]) {
        // バリデーション失敗時の処理
        showValidationErrors(errors)
    }
}

// カスタム年齢バリデーションルール
class AgeRule: Rule {
    private var message: String
    
    init(message: String = "年齢は18歳以上120歳以下で入力してください") {
        self.message = message
    }
    
    func validate(_ value: String) -> Bool {
        guard let age = Int(value) else { return false }
        return age >= 18 && age <= 120
    }
    
    func errorMessage() -> String {
        return message
    }
}

リアルタイムバリデーション

extension SignUpViewController: UITextFieldDelegate {
    func textFieldDidEndEditing(_ textField: UITextField) {
        // フォーカスが外れた時にそのフィールドをバリデーション
        validator.validateField(textField) { [weak self] error in
            DispatchQueue.main.async {
                if let error = error {
                    self?.showFieldError(textField, error: error)
                } else {
                    self?.clearFieldError(textField)
                }
            }
        }
    }
    
    private func showFieldError(_ textField: UITextField, error: ValidationError) {
        // エラー表示ロジック
        textField.layer.borderColor = UIColor.red.cgColor
        textField.layer.borderWidth = 1.0
        
        if let errorLabel = getErrorLabel(for: textField) {
            errorLabel.text = error.errorMessage
            errorLabel.isHidden = false
        }
    }
    
    private func clearFieldError(_ textField: UITextField) {
        // エラークリアロジック
        textField.layer.borderColor = UIColor.systemGray4.cgColor
        textField.layer.borderWidth = 1.0
        
        if let errorLabel = getErrorLabel(for: textField) {
            errorLabel.isHidden = true
        }
    }
}

テスト

import XCTest
@testable import YourApp
import SwiftValidator

class ValidationTests: XCTestCase {
    var validator: Validator!
    var testTextField: UITextField!
    
    override func setUp() {
        super.setUp()
        validator = Validator()
        testTextField = UITextField()
    }
    
    func testEmailValidation() {
        validator.registerField(testTextField, 
                               rules: [RequiredRule(), EmailRule()])
        
        // 有効なメールアドレス
        testTextField.text = "[email protected]"
        
        let expectation = self.expectation(description: "Validation completed")
        
        validator.validateField(testTextField) { error in
            XCTAssertNil(error, "有効なメールアドレスでエラーが発生しました")
            expectation.fulfill()
        }
        
        wait(for: [expectation], timeout: 1.0)
    }
    
    func testInvalidEmailValidation() {
        validator.registerField(testTextField, 
                               rules: [RequiredRule(), EmailRule()])
        
        // 無効なメールアドレス
        testTextField.text = "invalid-email"
        
        let expectation = self.expectation(description: "Validation completed")
        
        validator.validateField(testTextField) { error in
            XCTAssertNotNil(error, "無効なメールアドレスでエラーが発生しませんでした")
            expectation.fulfill()
        }
        
        wait(for: [expectation], timeout: 1.0)
    }
    
    func testCustomRule() {
        let customRule = AgeRule()
        validator.registerField(testTextField, rules: [customRule])
        
        // 有効な年齢
        testTextField.text = "25"
        
        let expectation = self.expectation(description: "Validation completed")
        
        validator.validateField(testTextField) { error in
            XCTAssertNil(error, "有効な年齢でエラーが発生しました")
            expectation.fulfill()
        }
        
        wait(for: [expectation], timeout: 1.0)
    }
}

ベストプラクティス

1. エラーメッセージの国際化

// Localizable.strings
"validation.required" = "この項目は必須です";
"validation.email" = "正しいメールアドレスを入力してください";
"validation.minLength" = "最低%d文字で入力してください";

// 使用例
RequiredRule(message: NSLocalizedString("validation.required", comment: ""))
EmailRule(message: NSLocalizedString("validation.email", comment: ""))

2. パフォーマンスの最適化

// 重いバリデーションは非同期で実行
class AsyncValidationRule: Rule {
    func validate(_ value: String) -> Bool {
        // 重い処理は別スレッドで実行
        return true
    }
}

3. アクセシビリティ対応

// エラーラベルにアクセシビリティ情報を追加
errorLabel.accessibilityLabel = "エラーメッセージ"
errorLabel.accessibilityTraits = .staticText

// テキストフィールドとエラーラベルを関連付け
textField.accessibilityDescribedBy = errorLabel

まとめ

SwiftValidatorは、iOSアプリケーションのフォームバリデーションを簡単かつ効率的に実装できる優れたライブラリです。ルールベースのアーキテクチャにより、複雑なバリデーションロジックも分かりやすく管理できます。

主な利点

  • シンプルなAPI: 直感的で使いやすいインターフェース
  • 拡張性: カスタムルールの作成が容易
  • UIKitとの統合: UITextFieldとの自然な統合
  • エラーハンドリング: 詳細なエラー情報とUI更新
  • テスト可能性: ユニットテストの作成が容易

SwiftValidatorを使用することで、堅牢で使いやすいフォームバリデーション機能をiOSアプリケーションに簡単に追加できます。