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:
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:
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.
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.
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.
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.
"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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With