Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> iOS

Cách validate TextField chung (generic) trong Swift

Khi phát triển app, thường phải validate nhiều trường input (đăng ký, đăng nhập...). Viết validation riêng cho từng field lặp code, dễ sai sót. Giải pháp: **Generic Validation Library** - viết một lần, dùng cho mọi field.

Ưu điểm

  • Code tái sử dụng (reusable)
  • Giảm lỗi con người
  • Setup nhanh, hook vào code dễ dàng
  • Sửa một chỗ → ảnh hưởng toàn bộ
  • Dễ maintain, test

Triển khai Validation Library

Bước 1: Tạo class Validation

File → New → Swift File → Validation.swift


import Foundation

class Validation {
    
    // MARK: - Name Validation
    /// Tên: 3-18 ký tự, chỉ chữ/c số/_ (tuỳ chỉnh regex theo nhu cầu)
    func validateName(_ name: String) -> Bool {
        let nameRegex = "^\\w{3,18}$"
        let trimmed = name.trimmingCharacters(in: .whitespaces)
        let predicate = NSPredicate(format: "SELF MATCHES %@", nameRegex)
        return predicate.evaluate(with: trimmed)
    }
    
    // MARK: - Phone Validation (Vietnam format)
    /// SĐT VN: 10-11 số, bắt đầu bằng 0 + (3|5|7|8|9)
    func validatePhoneNumber(_ phoneNumber: String) -> Bool {
        let phoneRegex = "^(0[3|5|7|8|9])+([0-9]{8,9})$"
        let trimmed = phoneNumber.trimmingCharacters(in: .whitespaces)
        let predicate = NSPredicate(format: "SELF MATCHES %@", phoneRegex)
        return predicate.evaluate(with: trimmed)
    }
    
    // MARK: - Email Validation
    func validateEmail(_ email: String) -> Bool {
        let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let trimmed = email.trimmingCharacters(in: .whitespaces)
        let predicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
        return predicate.evaluate(with: trimmed)
    }
    
    // MARK: - Password Validation
    /// Tối thiểu 8 ký tự, ít nhất 1 chữ cái và 1 số
    func validatePassword(_ password: String) -> Bool {
        let passRegex = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$"
        let trimmed = password.trimmingCharacters(in: .whitespaces)
        let predicate = NSPredicate(format: "SELF MATCHES %@", passRegex)
        return predicate.evaluate(with: trimmed)
    }
    
    // MARK: - Generic Validation
    /// Validate bất kỳ field nào với regex tùy chỉnh
    func validate(_ text: String, regex: String) -> Bool {
        let trimmed = text.trimmingCharacters(in: .whitespaces)
        let predicate = NSPredicate(format: "SELF MATCHES %@", regex)
        return predicate.evaluate(with: trimmed)
    }
}

Bước 2: Sử dụng trong ViewController


import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var phoneTextField: UITextField!
    
    let validation = Validation()
    
    @IBAction func validateButtonTapped(_ sender: Any) {
        // 1. Unwrap an toàn
        guard let name = nameTextField.text,
              let email = emailTextField.text,
              let password = passwordTextField.text,
              let phone = phoneTextField.text else {
            print("Có trường trống")
            return
        }
        
        // 2. Validate từng field
        if !validation.validateName(name) {
            showAlert("Tên không hợp lệ (3-18 ký tự)")
            return
        }
        
        if !validation.validateEmail(email) {
            showAlert("Email không đúng định dạng")
            return
        }
        
        if !validation.validatePassword(password) {
            showAlert("Mật khẩu: tối thiểu 8 ký tự, có chữ và số")
            return
        }
        
        if !validation.validatePhoneNumber(phone) {
            showAlert("Số điện thoại VN không hợp lệ")
            return
        }
        
        // 3. Tất cả hợp lệ
        showAlert("✅ Tất cả trường đều hợp lệ!")
    }
    
    func showAlert(_ message: String) {
        let alert = UIAlertController(title: "Thông báo", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

Mở rộng: Validation với Error Messages chi tiết


struct ValidationResult {
    let isValid: Bool
    let errorMessage: String?
}

class Validator {
    static func validate(_ text: String?, rules: [ValidationRule]) -> ValidationResult {
        guard let text = text, !text.isEmpty else {
            return ValidationResult(isValid: false, errorMessage: "Trường này không được để trống")
        }
        
        for rule in rules {
            if !rule.check(text) {
                return ValidationResult(isValid: false, errorMessage: rule.errorMessage)
            }
        }
        return ValidationResult(isValid: true, errorMessage: nil)
    }
}

struct ValidationRule {
    let check: (String) -> Bool
    let errorMessage: String
    
    // Predefined rules
    static let required = ValidationRule(
        check: { !$0.trimmingCharacters(in: .whitespaces).isEmpty },
        errorMessage: "Bắt buộc nhập"
    )
    
    static let email = ValidationRule(
        check: { NSPredicate(format: "SELF MATCHES %@", "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}").evaluate(with: $0) },
        errorMessage: "Email không hợp lệ"
    )
    
    static func minLength(_ length: Int) -> ValidationRule {
        ValidationRule(
            check: { $0.count >= length },
            errorMessage: "Tối thiểu \(length) ký tự"
        )
    }
    
    static func regex(_ pattern: String, message: String) -> ValidationRule {
        ValidationRule(
            check: { NSPredicate(format: "SELF MATCHES %@", pattern).evaluate(with: $0) },
            errorMessage: message
        )
    }
}

// Usage
let result = Validator.validate(emailTextField.text, rules: [.required, .email])
if !result.isValid { showError(result.errorMessage!) }

Thư viện Validation phổ biến (Production)

  • Validator (Swift): pod 'Validator' - Declarative, chainable
  • RxSwift + RxCocoa: Reactive validation với BehaviorRelay
  • Combine (iOS 13+): Built-in reactive validation

Kết luận

Generic validation giúp code sạch, maintainable, ít bug. Bắt đầu với class Validation đơn giản, sau đó có thể mở rộng sang struct-based rule system hoặc dùng thư viện chuyên biệt cho project lớn.