Swift中为App引导页TextField输入构建验证器的合适设计模式?
嗨,这个问题我之前做引导页的时候也碰到过!在Swift里实现灵活且不冗余的TextField验证,策略模式或者基于协议的验证器方案绝对是最优解,既能轻松扩展不同规则,又能避免在代理方法里写一堆乱糟糟的if-else。下面我给你一步步拆解具体实现:
一、先定义验证协议和基础验证器
首先我们抽象出一个验证协议,让所有验证规则都遵循这个协议,这样后续新增规则只需要实现协议即可,完全不影响原有代码。
// 定义验证协议,统一验证方法和返回结果 protocol TextFieldValidator { func validate(_ text: String?) -> ValidationResult } // 封装验证结果,包含成功状态和错误提示信息 enum ValidationResult { case success case failure(message: String) } // 邮箱验证器:实现具体的邮箱格式校验逻辑 struct EmailValidator: TextFieldValidator { func validate(_ text: String?) -> ValidationResult { guard let email = text, !email.isEmpty else { return .failure(message: "请输入邮箱地址") } // 这里用了通用的邮箱正则,你可以根据需求调整 let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailRegex) return emailPredicate.evaluate(with: email) ? .success : .failure(message: "请输入有效的邮箱格式") } } // 出生日期验证器:实现日期格式+合理性校验 struct DateOfBirthValidator: TextFieldValidator { func validate(_ text: String?) -> ValidationResult { guard let dobText = text, !dobText.isEmpty else { return .failure(message: "请输入出生日期") } // 假设用户输入的是yyyy-MM-dd格式,你可以根据实际输入习惯调整格式 let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" dateFormatter.locale = Locale(identifier: "en_US_POSIX") guard let dobDate = dateFormatter.date(from: dobText) else { return .failure(message: "请输入正确的日期格式(yyyy-MM-dd)") } // 额外校验:出生日期不能是未来日期 if dobDate > Date() { return .failure(message: "出生日期不能晚于当前日期") } return .success } }
二、给TextField绑定专属验证器
我们可以通过关联对象给UITextField扩展一个validator属性,这样每个TextField可以直接绑定对应的验证器,不用在代理方法里判断是哪个TextField。
import ObjectiveC // 用关联对象给UITextField添加验证器属性 private var ValidatorAssociationKey: UInt8 = 0 extension UITextField { var validator: TextFieldValidator? { get { return objc_getAssociatedObject(self, &ValidatorAssociationKey) as? TextFieldValidator } set { objc_setAssociatedObject(self, &ValidatorAssociationKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } }
三、在ViewController中统一处理验证
现在在引导页的ViewController里,只需要给每个TextField绑定对应验证器,然后在textFieldDidEndEditing里统一调用验证逻辑即可:
class OnboardingVC: UIViewController, UITextFieldDelegate { @IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var dobTextField: UITextField! override func viewDidLoad() { super.viewDidLoad() // 给每个TextField绑定对应的验证器 emailTextField.validator = EmailValidator() dobTextField.validator = DateOfBirthValidator() // 设置代理 emailTextField.delegate = self dobTextField.delegate = self } func textFieldDidEndEditing(_ textField: UITextField) { // 取出当前TextField的验证器,执行验证 guard let validator = textField.validator else { return } let result = validator.validate(textField.text) // 根据验证结果给用户反馈 switch result { case .success: // 验证通过:恢复正常样式,清空错误提示 textField.layer.borderColor = UIColor.systemGray.cgColor textField.layer.borderWidth = 1.0 // 如果有错误提示标签,这里可以隐藏 case .failure(let message): // 验证失败:显示错误样式+提示用户 textField.layer.borderColor = UIColor.systemRed.cgColor textField.layer.borderWidth = 1.0 let alert = UIAlertController(title: "输入有误", message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "好的", style: .default)) present(alert, animated: true) } } }
四、后续扩展优化
如果之后需要新增其他验证规则(比如手机号、密码强度),只需要新建一个遵循TextFieldValidator的结构体即可,完全不用修改原有代码,完美符合开闭原则。
另外你还可以把错误反馈的逻辑封装成TextField的扩展,让样式更统一:
extension UITextField { func applyValidationStyle(_ result: ValidationResult) { switch result { case .success: self.layer.borderColor = UIColor.systemGray.cgColor self.layer.borderWidth = 1.0 self.rightView = nil case .failure: self.layer.borderColor = UIColor.systemRed.cgColor self.layer.borderWidth = 1.0 // 给TextField右侧加个错误图标 let errorIcon = UIImageView(image: UIImage(systemName: "exclamationmark.triangle")) errorIcon.tintColor = .systemRed errorIcon.frame = CGRect(x: 0, y: 0, width: 20, height: 20) self.rightView = errorIcon self.rightViewMode = .always } } }
然后在textFieldDidEndEditing里直接调用textField.applyValidationStyle(result)就可以了,代码更简洁。
内容的提问来源于stack exchange,提问作者D.Rees
相关产品推荐
相关产品推荐

