A Functional Approach to UITextField Validation

It is very common for an app to validate user input and provide instant feedback when something is typed incorrectly. In this article, I will walk through the implementation I worked on to validate a text field’s string. I decided to attempt a functional approach and found a solution that is reusable and scalable for every kind of validation.

Identifying the Functions

One thing that the functional programming paradigm urges us to do is to think how computation can be broken down into functions that reliably take an input and produce an output, without modifying state.

When I approached this problem, my first step was to identify what kind of data I wanted to deal with. I figured that the entry data would always be a String type and the result a Bool. So I’d write validation functions that would take a string as an argument and return true or false.

With that in mind, I wanted another function that would allow me to take an unlimited amount of validation functions, compute the result of all of them, and return the final result. I wrote a function that would take an array of functions with the same signature as the validation functions `(String -> Bool) that function would return a Bool as the final result of evaluating all validation function.

For those more familiar with functional concepts, this is a take on using a “pipeline” – a sequence of operations that are applied in succession to, generally, produce a value.

Implementation

I decided to create a UITextField extension and came up with an implementation using map and reduce, two functions commonly used in functional programming:

extension UITextField {

    func validateField(_ functions: [(String) -> Bool]) -> Bool {
        return functions.map { f in f(self.text ?? "") }.reduce(true) { $0 && $1 }
    }

}

validateField iterates over the functions array and apply each function with the current UITextField text, reduces each result into the final result and returns it.

All I needed after was a bunch of validation functions taking a String as argument and returning a Bool:

func isPhoneNumberValid(text: String) -> Bool
func isZipCodeValid(text: String) -> Bool
func isStateValid(text: String) -> Bool
func isCVCValid(text: String) -> Bool

This functional approach gave me some consistency through all the validation functions, and thanks to that I found a way to use all of them the same way in a clean, short function.

Evaluation with regular expression

For the actual implementation of these validation functions, I created an evaluate function as part of a String extension that would take a regular expression as an argument and return a boolean based on the evaluation:

extension String {

    func evaluate(with condition: String) -> Bool {
        guard let range = range(of: condition, options: .regularExpression, range: nil, locale: nil) else {
            return false
        }

        return range.lowerBound == startIndex && range.upperBound == endIndex
    }

}

I could then use this evaluate function for each validation function:

func isPhoneNumberValid(text: String) -> Bool {
    let regexp = "^[0-9]{10}$"
    return text.evaluate(with: regexp)
}

func isZipCodeValid(text: String) -> Bool {
    let regexp = "^[0-9]{5}$"
    return text.evaluate(with: regexp)
}

func isStateValid(text: String) -> Bool {
    let regexp = "^[A-Z]{2}$"
    return text.evaluate(with: regexp)
}

func isCVCValid(text: String) -> Bool {
    let regexp = "^[0-9]{3,4}$"
    return text.evaluate(with: regexp)
}

func isEmailValid(text: String) -> Bool {
    let regexp = "[A-Z0-9a-z._]+@([\\w\\d]+[\\.\\w\\d]*)"
    return text.evaluate(with: regexp)
}

And here is how I call my validation implementation on any text field:

textField.validateField([isStateValid])

The beauty is that any number of validation functions can be added to this list and validateField will work reliably. Moreover, it is trivial for other developers to read such a descriptive list of validations and understand what is required of the field.

Testing

Testing this code is also trivial:

func test_whenTextFieldIsValidState() {
    // Given
    let textField = UITextField()
    // When
    textField.text = "NY"
    // Then
    XCTAssertTrue(textField.isStateValid(textField.text!))
}

func test_whenTextFieldIsInvalidEmail_withUnderscoreCharacter() {
    // Given
    let textField = UITextField()
    // When
    textField.text = "thibault+test@_gmail.com"
    // Then
    XCTAssertFalse(textField.isEmailValid(textField.text!))
}

Conclusion

This implementation gives me a lot of options if I want to combine multiple validation functions for a text field – I simply create an extra validation function and apply it. Moreover, it provides a very descriptive way to understand what validations need to take place for a given text field. And, it is very easy to test.

Though many iOS developers are not familiar with functional concepts, it sometimes pays off to start by thinking through how to solve a small problem in a functional way. Creating functions with minimal responsibilities can make code clean and concise. It made my code easy to test and scale. On top of that this functional approach perfectly interoperates with my object-oriented code.

So far this functional solution has proven to be very efficient for the projects I’ve used it in, and I hope it will be useful for you as well! You’ll find a Xcode playground in this repository that has all the code I used in this article.

Categories: