Form validation in Swift
"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
}
Another option that seems good so far is SwiftValidator. It is an active project and only took a few minutes for me to setup in my project.
https://github.com/jpotts18/SwiftValidator