Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Form validation in Swift

I have recently commenced working on a Swift (2) iOS project, and started to face circumstances where I have forms with many fields which need validating.

I have a predominantly .Net background, and with Binding and Annotations it is possible to implement form validation cleanly in a low maintenance manner, on large forms, with few lines of code.

Since embracing Swift, I have come across many examples detailing validation in various fashions, but everything I have encountered thus far seems very labour intensive and high maintenance, or the explanations focus on conducting the validation itself rather than connecting the validation process between the model and the view efficiently.

For example:

  • Validating TextField
  • Validate TextField as Double using extensions
  • Validating email addresses

My current solution defines extensions which can be checked in a function, rule-by-rule, when validating field input, however I feel there must be a more scalable solution to this problem.

What approaches can be taken, when validating forms with potentially many inputs, that promote maintainability?

For example sake, we could discuss a hypothetical form with:

  • A textField requesting a number be entered (Int).
  • A textField requesting a decimal number be entered (Double).
  • A textField requesting a number be entered, matching specific rules (Eg, is prime) (Double).
  • A textField requesting a string be entered of some length kL.
  • A textField requesting a string be entered conforming to some custom rule.

Of course, I am not looking for an implementation that literally satisfies the list above, but instead a method or approach that is scaleable across these types of scenarios.

like image 480
Élie Avatar asked Mar 30 '16 08:03

Élie


People also ask

How do you validate in Swift?

So, we can minimize the code, and manage it well, by creating an extension of UITextField . Then, inside it, create a function that will be used to validate the TextField . That function should return a Bool value, as shown below: This is just a custom validation; you can create a validation based on your needs.

What is form validation?

Form validation is a “technical process where a web-form checks if the information provided by a user is correct.” The form will either alert the user that they messed up and need to fix something to proceed, or the form will be validated and the user will be able to continue with their registration process.

What is form validation explain with example?

Form validation generally performs two functions. Basic Validation − First of all, the form must be checked to make sure all the mandatory fields are filled in. It would require just a loop through each field in the form and check for data.


1 Answers

"Ugh, forms"

-Sir Albert Einstein

Yes, building a scaleable form in iOS can be a difficult and monotonous job. Which is why I have a base class called a FormViewController that exposes a few common validation methods and a few methods that you can use to add customised validation.

Now, the following code could be very long, and I am not going to explain each line. Do revert in the form of comments, if you have any doubts.

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
}
like image 134
avismara Avatar answered Oct 05 '22 07:10

avismara