In numerous apps, across multiple platforms (iOS, Android, and Windows), I have been tasked to validate user input either after the user has entered it or as it is entered. This validation can be done very easily with regular expressions; however, we do not want various regular expression strings littered throughout our code. We can solve this problem by creating different classes or structures that contain the validation code. The question is; how would we organize these types to make them easy to use and maintain? Prior to protocol extensions in Swift, I would have used protocols to define the validation requirements and would then create a type that would conform to the protocol for each validation type needed.
Prior to looking at how we would accomplish this text validation, let's take a quick look at what regular expressions are and how we would use them in Swift. A regular expression (also known as regex) is a special text string that is used to describe a search or matching pattern. The regular expression string, also known as a pattern, consists of one or more characters, operators, or constructs. Regular expressions are very useful when searching a string for a particular pattern or (as we use it here) validating a string.
Regular expressions are not unique to Swift. Almost all modern languages have a way to use regular expressions. Whole books have been written about regular expressions so in this section we will give a very brief introduction with enough information for you to understand the examples in this chapter.
In its simplest form, a regular expression is a string of characters such as abc
or 12345
. Using a regular expression such as this will match the pattern within a string, as shown in the following examples:
Regex |
Matches |
Description |
---|---|---|
abc |
xyzabcxyzabc |
Matches the string |
12345 |
1234567890 |
Matches the string |
We can also define character sets using square brackets ([ ]
). Character sets will match one character in the string to any character within the set. To define the set, we can use a string of characters, as shown in the last example, or we can use the – (minus sign) operator to specify a range:
Regex |
Matches |
Description |
---|---|---|
[abc] |
xyzabcxyz |
Matches any character in the set |
[a-zA-Z] |
xyzabcxyz |
Matches any lower or uppercase letter |
We use curly brackets ({ }
) to specify the amount of repetition so we can match more than one character. For example, if we used {2,5}
then that would mean we want to match at least 2
characters but no more than 5
characters:
Regex |
Matches |
Description |
---|---|---|
|
xyzab
|
Matches |
|
xyzab
|
Matches |
The caret (^
) at the beginning means we want to match at the beginning and the dollar sign ($
) means match at the end. We can use these two special characters to match a full string. For example, the ^[a-z]{0,5}$
pattern will match a string only if there are between 0
and 5
lowercase letters. The match will fail if there are any other characters besides lowercase letters or more than five characters:
Regex |
Matches |
Description |
---|---|---|
|
|
Fails more than five characters |
|
xyz12 |
Matches five lowercase or number characters |
Finally, let's look at is some additional special characters within regular expressions. These are characters that need to be escaped using the backslash (\
) and have special meaning:
Character |
Definition |
---|---|
|
The dot matches any single character |
|
Matches a newline character |
|
Matches a tab |
|
Matches a digit [0-9] |
|
Matches a non-digit |
|
Matches an alphanumeric character [a-zA-Z0-9] |
|
Matches a non-alphanumeric character |
|
Matches a whitespace character |
|
Matches a non-whitespace character |
There is a lot more to regular expressions than we have just seen. In this section, we only gave enough information to help you understand the text validation examples in this chapter. If you plan on using regular expressions on a regular (pun intended) basis, I would suggest reading more about them.
Now let's look at how we would develop our validation framework without protocol extensions. We will begin by defining a TextValidationProtocol
protocol that will define the requirements for any type that we will use for text validation. This will allow us to use the TextValidationProtocol
protocol in place of implementation types. If you recall, this is a form of polymorphism:
protocol TextValidationProtocol { var regExMatchingString: String {get} var regExFindMatchString: String {get} var validationMessage: String {get} func validateString(str: String) -> Bool func getMatchingString(str: String) -> String? }
In this protocol we define three properties and two methods that any type that conforms to the TextValidationProtocol
protocol must implement. The three properties are:
regExMatchingString
: Regular expression string used to verify that the input string contains only valid characters.regExFindMatchString
: Regular expression string used to retrieve a new string from the input string that contains only valid characters. This regular expression is generally used when we need to validate the input in real time, as the user enters information, because it will remove all characters starting with the first invalid characters to the end of the string.validationMessage
: This is the error message to display if the input string contained non-valid characters.The two methods for this protocol are:
validateString
: This method will return true
if the input string contains only valid characters. The regExMatchingString
property will be used in this method to perform the match.getMatchingString
: This method will return a new string that contains only valid characters. This method is generally used when we need to validate the input in real time, as the user enters information, because it will remove all characters starting with the first invalid characters. We will use the regExFindMatchString
property in this method to retrieve the new string.Now let's see how we would create a class that conforms to this protocol. The following class would be used to verify that the input string contains 0
to 10
alpha characters:
class AlphaValidation1: TextValidationProtocol { static let sharedInstance = AlphaValidation1() private init(){} let regExFindMatchString = "^[a-zA-Z]{0,10}" let validationMessage = "Can only contain Alpha characters" var regExMatchingString: String { get { return regExFindMatchString + "$" } } func validateString(str: String) -> Bool { if let _ = str.rangeOfString(regExMatchingString, options: .RegularExpressionSearch) { return true } else { return false } } func getMatchingString(str: String) -> String? { if let newMatch = str.rangeOfString(regExFindMatchString, options: .RegularExpressionSearch) { return str.substringWithRange(newMatch) } else { return nil } } }
In this implementation, the regExFindMatchString
and validationMessage
properties are stored properties and the regExMatchingString
property is a computed property. We also implement the validateString()
and getMatchingString()
methods within the class to conform to the protocol.
Normally, we would have several different types that conform to the TextValidationProtocol
protocol where each one would validate a different type of input. As we can see from the AlphaValidation1
class, there is quite a bit of code involved with each validation type. A lot of the code would need to be duplicated for each validation type. This is not ideal. However, if we wanted to avoid creating a class hierarchy with a super class containing the duplicate code, we would have no other choice. Protocol extensions give us a better option. Let's take a look at how we would implement our text validation types with protocol extensions.
With protocol extensions, we need to think about the code a little differently. The big difference is that we do not need, nor want to define, everything in the protocol. With standard protocols or when we use a class hierarchy, all methods and properties that we want to access using the interface provided by the generic superclass or protocol type have to be defined within the superclass or protocol. With protocol extensions, it is actually preferable for us to not define a computed property or method in the protocol if we are going to implement it within the protocol extension. Therefore, when we rewrite our text validation types with protocol extensions, the TextValidationProtocol
protocol would be greatly simplified and would look like this:
protocol TextValidationProtocol { var regExFindMatchString: String {get} var validationMessage: String {get} }
In the original TextValidationProtocol
protocol, we defined three properties and two methods. As we can see in this new protocol, we are only defining two properties. Now that we have our TextValidationProtocol
defined, let's create a protocol extension where we implement the other two methods and the computed property:
extension TextValidationProtocol { var regExMatchingString: String { get { return regExFindMatchString + "$" } } func validateString(str: String) -> Bool { if let _ = str.rangeOfString(regExMatchingString, options: .RegularExpressionSearch) { return true } else { return false } } func getMatchingString(str: String) -> String? { if let newMatch = str.rangeOfString(regExFindMatchString, options: .RegularExpressionSearch) { return str.substringWithRange(newMatch) } else { return nil } } }
In the TextValidationProtocol
protocol extension, we implement the two methods and the computed property that were defined in the original TextValidationProtocol
protocol but not defined in the new one.
Now that we have created our protocol and protocol extension, we are able to define our text validation types. In the following code we define three classes that we will use to validate text:
class AlphaValidation: TextValidationProtocol { static let sharedInstance = AlphaValidation() private init(){} let regExFindMatchString = "^[a-zA-Z]{0,10}" let validationMessage = "Can only contain Alpha characters" } class AlphaNumericValidation: TextValidationProtocol { static let sharedInstance = AlphaNumericValidation() private init(){} let regExFindMatchString = "^[a-zA-Z0-9]{0,15}" let validationMessage = "Can only contain Alpha Numeric characters" } class DisplayNameValidation: TextValidationProtocol { static let sharedInstance = DisplayNameValidation() private init(){} let regExFindMatchString = "^[\\s?[a-zA-Z0-9\\-_\\s]]{0,15}" let validationMessage = "Display Name can contain only contain Alphanumeric Characters" }
In each of the text validation classes, we create a static constant and a private initiator so we can use the class as a singleton. For more information on the singleton pattern please see the Singleton Design Pattern section of Chapter 6, Adopting Design Patterns in Swift.
After we define the singleton pattern, all we do in each type is set the values for the regExFindMatchString
and the validationMessage
properties. Now we have virtually no duplicate code between the types except the code to implement the singleton pattern. Even if we could, we would not want to define the singleton code in the protocol extension because we would not want to force that pattern on all conforming types. We can also see that we are able to define these three classes with less code than it took to define the one class without protocol extensions.
We could use these validation classes like this:
var myString1 = "abcxyz" var myString2 = "abc123" var validation = AlphaValidation.sharedInstance validation.validateString(myString1) validation.validateString(myString2) validation.getMatchingString(myString1) validation.getMatchingString(myString2)
In this example, we create two String
types, each containing a different string value. We then get the shared instance of the AlphaValidation
type. We use the validateString()
method of the AlphaValidation
instance to validate the strings, which verifies that the whole string matches the regular expression pattern defined in the AlphaValidation
instance. We then use the getMatchingString()
method of the AlphaValidation
instance to return a new string that contains only the valid characters defined in the regular expression pattern.
The validateString()
method returns a true
value for the myString1
instance because the value of myString1
matches the regular expression pattern; however it returns a false
value for the myString2
instances because their value contains numbers which do not match the ^[a-zA-Z]{0,10}
regular express pattern defined in the AlphaValidation
type.
The getMatchingString()
method returns the full value of myString1
because the value matches the regular express pattern defined in the AlphaValidation
type. However, for the value of the myString2
instance it only returns an instance of the string type that contains the value of abc because that is the only part of the myString2
value that matches the pattern.
Let's look at how we could use the text validation code to validate input into a UITextField
in an iOS application. We will not go through how to add a UITextField
in a storyboard or wiring up the UITextField
with our backend code but you can take a look at the sample application included with the downloadable code for this book to see how it all works together. To start with, we would want to create a Dictionary
object that maps the UITextField
instances to the validation classes like this:
var validators = [UITextField: TextValidationProtocol]()
We could then populate the validators
dictionary as shown here:
validators[alphaTextField!] = AlphaValidation.sharedInstance validators[alphaNumericTextField!] = AlphaNumericValidation.sharedInstance validators[displayNameTextField!] = DisplayNameValidation.sharedInstance
We can now set the set the Editing Changed
event of the text fields to a single method. This method would look like this:
@IBAction func keyPressed(textField: UITextField) { if let validator = validators[textField] where !validator.validateString(textField.text!) { textField.text = validator.getMatchingString(textField.text!) messageLabel?.text = validator.validationMessage } }
In this method, we use the if let validator = validators[textField]
statement to retrieve the validator for the particular text field and then we use the where !validator.validateString(textField.text!)
statement to validate the string that the user has entered. If the string fails validation, we use the getMatchingString()
method to update the text in the text field by removing all characters from the input string, starting with the first invalid character, and then display the error message from the text validation class.
In the downloadable code for this book, you will find a sample project that demonstrates how to use text validation types.