【问题标题】:Form validation in SwiftSwift 中的表单验证
【发布时间】:2016-07-18 03:37:18
【问题描述】:

我最近开始了一个 Swift (2) iOS 项目,并开始面临我的表单包含许多需要验证的字段的情况。

我的背景主要是 .Net,通过绑定和注释,可以在大型表单上以低维护的方式干净利落地实现表单验证,并且只需要几行代码。

自从使用 Swift 以来,我遇到了许多以各种方式详细说明验证的示例,但到目前为止,我遇到的所有事情似乎都非常耗费人力且维护成本很高,或者解释集中在进行验证本身而不是连接验证过程模型和视图有效。

例如:

我当前的解决方案定义了可以在验证字段输入时在函数中逐条检查的扩展,但是我觉得必须有一个更具可扩展性的解决方案来解决这个问题。

在验证可能有很多输入的表单时,可以采取哪些方法来提高可维护性?

例如,我们可以讨论一个假设的形式:

  • 要求输入数字的文本字段 (Int)。
  • 要求输入十进制数的文本字段(双精度)。
  • 请求输入数字的文本字段,匹配特定规则(例如,是素数)(双)。
  • 要求输入长度为 kL 的字符串的文本字段。
  • 要求输入符合某些自定义规则的字符串的文本字段。

当然,我不是在寻找真正满足上面列表的实现,而是在这些类型的场景中可扩展的方法或方法。

【问题讨论】:

    标签: ios swift validation


    【解决方案1】:

    “呃,表格”

    -阿尔伯特·爱因斯坦爵士

    是的,在 iOS 中构建可扩展的表单可能是一项艰巨而单调的工作。这就是为什么我有一个名为 FormViewController 的基类,它公开了一些常见的验证方法和一些可用于添加自定义验证的方法。

    现在,下面的代码可能很长,我不会解释每一行。如果您有任何疑问,请以 cmets 的形式恢复。

    import UIKit
    
    typealias TextFieldPredicate = ( (String) -> (Bool) )
    
    class FormViewController : UIViewController {
    
        var activeTextField : UITextField!
    
        private var mandatoryFields  = [UITextField]()
        private var emptyErrorMessages = [String]()
    
        private var emailFields = [UITextField]()
        private var emailErrorMessages = [String]()
    
        private var specialValidationFields = [UITextField]()
        private var specialValidationMethods = [TextFieldPredicate]()
        private var specialValidationErrorMessages = [String]()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // Do any additional setup after loading the view.
        }
    
        override func viewWillAppear(animated: Bool) {
            super.viewWillAppear(animated)
            registerForNotifications()
        }
    
        override func viewWillDisappear(animated: Bool) {
            super.viewWillDisappear(animated)
            NSNotificationCenter.defaultCenter().removeObserver(self)
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
    
        private func registerForNotifications() {
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(FormViewController.keyboardWillShow(_:)), name:UIKeyboardWillShowNotification, object: nil);
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(FormViewController.keyboardWillHide(_:)), name:UIKeyboardWillHideNotification, object: nil);
        }
    
        func keyboardWillShow(notification:NSNotification?) {
            let keyboardSize = notification?.userInfo![UIKeyboardFrameBeginUserInfoKey]!.CGRectValue.size
            self.view.frame.origin.y = 0
            let keyboardYPosition = self.view.frame.size.height - keyboardSize!.height
            if keyboardYPosition < self.activeTextField!.frame.origin.y {
                UIView.animateWithDuration(GlobalConstants.AnimationTimes.SHORT) { () -> Void in
                    self.view.frame.origin.y = self.view.frame.origin.y - keyboardSize!.height + 30
                }
            }
        }
    
        func keyboardWillHide(notification:NSNotification?) {
            UIView.animateWithDuration(GlobalConstants.AnimationTimes.SHORT) { () -> Void in
                self.view.frame.origin.y = 0
            }
        }
    
        func validateEmailForFields(emailTextFields:[UITextField]) -> [Bool] {
            var validatedBits = [Bool]()
            for emailTextField in emailTextFields {
                if let text = emailTextField.text where !text.isValidEmail() {
                    emailTextField.shakeViewForTimes(WelcomeViewController.ERROR_SHAKE_COUNT)
                    validatedBits.append(false)
                } else {
                    validatedBits.append(true)
                }
            }
            return validatedBits
        }
    
        func validateSpecialTextFields(specialTextFields:[UITextField]) -> [Bool] {
            var validatedBits = [Bool]()
            for specialTextField in specialTextFields {
                let specialValidationMethod = self.specialValidationMethods[ specialValidationFields.indexOf(specialTextField)!]
                validatedBits.append(specialValidationMethod(specialTextField.text!))
            }
            return validatedBits
        }
    
        func validateEmptyFields(textFields : [UITextField]) -> [Bool] {
            var validatedBits = [Bool]()
            for textField in textFields {
                if let text = textField.text where text.isEmpty {
                    textField.shakeViewForTimes(WelcomeViewController.ERROR_SHAKE_COUNT)
                    validatedBits.append(false)
                } else {
                    validatedBits.append(true)
                }
            }
            return validatedBits
        }
    
        func addMandatoryField(textField : UITextField, message : String) {
            self.mandatoryFields.append(textField)
            self.emptyErrorMessages.append(message)
        }
    
        func addEmailField(textField : UITextField , message : String) {
            textField.keyboardType = .EmailAddress
            self.emailFields.append(textField)
            self.emailErrorMessages.append(message)
        }
    
        func addSpecialValidationField(textField : UITextField , message : String, textFieldPredicate : TextFieldPredicate) {
            self.specialValidationErrorMessages.append(message)
            self.specialValidationMethods.append(textFieldPredicate)
            self.specialValidationFields.append(textField)
        }
    
        func errorMessageForEmptyTextField(textField : UITextField) throws -> String  {
            if self.mandatoryFields.contains(textField) {
                return self.emptyErrorMessages[self.mandatoryFields.indexOf(textField)!]
            } else {
                throw ValidationError.NonMandatoryTextField
            }
        }
    
        func errorMessageForMultipleEmptyErrors() -> String {
            return "Fields cannot be empty"
        }
    
        func errorMessageForMutipleEmailError() -> String {
            return "Invalid email addresses"
        }
    
        @IBAction func didTapFinishButton(sender:AnyObject?) {
            if let errorMessage = self.errorMessageAfterPerformingValidation() {
                self.showVisualFeedbackWithErrorMessage(errorMessage)
                return
            }
            self.didCompleteValidationSuccessfully()
        }
    
        func showVisualFeedbackWithErrorMessage(errorMessage : String) {
            fatalError("Implement this method")
        }
    
        func didCompleteValidationSuccessfully() {
    
        }
    
        func errorMessageAfterPerformingValidation() -> String? {
            if let errorMessage = self.errorMessageAfterPerformingEmptyValidations() {
                return errorMessage
            }
            if let errorMessage = self.errorMessageAfterPerformingEmailValidations() {
                return errorMessage
            }
            if let errorMessage = self.errorMessageAfterPerformingSpecialValidations() {
                return errorMessage
            }
            return nil
        }
    
        private func errorMessageAfterPerformingEmptyValidations() -> String? {
            let emptyValidationBits = self.performEmptyValidations()
            var index = 0
            var errorCount = 0
            var errorMessage : String?
            for validation in emptyValidationBits {
                if !validation {
                    errorMessage = self.emptyErrorMessages[index]
                    errorCount += 1
                }
                if errorCount > 1 {
                    return self.errorMessageForMultipleEmptyErrors()
                }
                index = index + 1
            }
            return errorMessage
        }
    
        private func errorMessageAfterPerformingEmailValidations() -> String? {
            let emptyValidationBits = self.performEmailValidations()
            var index = 0
            var errorCount = 0
            var errorMessage : String?
            for validation in emptyValidationBits {
                if !validation {
                    errorMessage = self.emailErrorMessages[index]
                    errorCount += 1
                }
                if errorCount > 1 {
                    return self.errorMessageForMutipleEmailError()
                }
                index = index + 1
            }
            return errorMessage
        }
    
        private func errorMessageAfterPerformingSpecialValidations() -> String? {
            let emptyValidationBits = self.performSpecialValidations()
            var index = 0
            for validation in emptyValidationBits {
                if !validation {
                    return self.specialValidationErrorMessages[index]
                }
                index = index + 1
            }
            return nil
        }
    
        func performEqualValidationsForTextField(textField : UITextField, anotherTextField : UITextField) -> Bool {
            return textField.text! == anotherTextField.text!
        }
    
    
        private func performEmptyValidations() -> [Bool] {
            return validateEmptyFields(self.mandatoryFields)
        }
        private func performEmailValidations() -> [Bool] {
            return validateEmailForFields(self.emailFields)
        }
        private func performSpecialValidations() -> [Bool] {
            return validateSpecialTextFields(self.specialValidationFields)
        }
    
    
    }
    
    extension FormViewController : UITextFieldDelegate {
        func textFieldDidBeginEditing(textField: UITextField) {
            self.activeTextField = textField
        }
        func textFieldDidEndEditing(textField: UITextField) {
            self.activeTextField = nil
        }
    }
    
    enum ValidationError : ErrorType {
        case NonMandatoryTextField
    }
    

    【讨论】:

    • 您的解决方案似乎是我应该采取的下一个合乎逻辑的步骤。感谢您的贡献。我特别喜欢它看起来非常独立,并展示了一些我会忽略的改进。
    • 谢谢,有一些依赖项(如shakeViewSHAKE_COUNT)。我应该很快重构它。
    【解决方案2】:

    EGFormValidator 库是用 Swift 3 编写的。它灵活且易于使用。

    【讨论】:

      【解决方案3】:

      目前看来不错的另一个选择是 SwiftValidator。 这是一个活跃的项目,我只需要几分钟就可以在我的项目中进行设置。

      https://github.com/jpotts18/SwiftValidator

      【讨论】:

        【解决方案4】:

        尽管已经晚了 3 年,但我还是把它放在这里,供任何想要灵活和可扩展选项的人使用。据我所知,这将完全适合作为问题的答案。

        请查看ATGValidator

        我没有在此处添加任何代码,因为框架链接中的自述文件包含大量相同的文档。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-21
          • 2015-05-20
          • 1970-01-01
          • 2017-06-04
          • 2021-02-08
          • 2019-09-03
          相关资源
          最近更新 更多