Update note: This tutorial was updated to iOS 8 and Swift by James Frost. Original post by Tutorial Team member Soheil Azarpour.
A regular expression (commonly known as a “regex”) is a string or a sequence of characters that specifies a pattern. Think of it as a search string — but with super powers!
A plain old search in a text editor or word processor will allow you to find simple matches. A regular expression can also perform these simple searches, but it takes things a step further and lets you search for patterns, such as two digits followed by a letter, or three letters followed by a hyphen.
This pattern matching allows you to do useful things like validate fields (phone numbers, email addresses), check user input, perform advanced text manipulation and much, much more.
If you have been eager to know more about using regular expressions in iOS, look no further than this tutorial — no previous experience required!
In this NSRegularExpression tutorial, you will implement code to search for patterns in text, replace those matches with whatever you wish, validate user input information, and find and highlight complex strings in a block of text.
In addition, I’ll give you a handy NSRegularExpression Cheat Sheet PDF that you can print out and use as reference as you’re developing, and a Swift playground which contains a load of examples that you can use to try out lots of different regular expressions! In fact, all of the examples of regular expressions that appear in this tutorial have live examples in the playground, so be sure to check them out.
Without further ado, it’s time to start crunching some regular expressions.
/The (Basics|Introduction)/
Note: If you’re already familiar with the basics of regular expressions, feel free to skip ahead to the section Implementing Regex in iOS.
If you are new to regular expressions and are wondering what all the hype is about, here’s a simple definition: regular expressions provide a way to search a given text document for matches to a specific pattern, and they may possibly alter the text based on those matches. There are many awesome books and tutorials written about regular expressions – you’ll find a short list of them at the end of this tutorial.
Regular Expressions Playground
In this tutorial, you’ll be creating a lot of regular expressions. If you want to try them out visually as you’re working with them, then a Swift playground is an excellent way to do so!
The starter project includes the playground for this tutorial. Once you’ve downloaded it, load up the project in Xcode and open iRegex.playground. You can also download the playground on its own.
The playground contains a number of functions at the top to highlight the search results from a regular expression within a piece of text, display a list of matches or groups in the results pane of the playground, and replace text. Don’t worry about the implementation of these methods for now though, as you’ll revisit them later. Instead, scroll down to the Basic Examples and Cheat Sheet sections and follow along with the examples.
In the results sidebar of the playground, you’ll see a list of matches alongside each example. For ‘highlight’ examples, you can hover over the result and click the eye or the empty circle icons to display the highlighted matches in the search text.
You’ll learn how to create NSRegularExpressions
later, but for now you can use this playground to get a feeling for how various regular expressions work, and to try out your own patterns. At any point you can use the Editor > Reset Playground menu item in Xcode to reset any of your changes.
Examples
Let’s start with a few brief examples to show you what regular expressions look like.
Here’s an example of a regular expression that matches the word “jump”:
jump
That’s about as simple as regular expressions get. You can use some APIs that are available in iOS to search a string of text for any part that matches this regular expression – and once you find a match, you can find where it is, or replace the text, etc.
Here’s a slightly more complicated example – this one matches either of the words “jump” or “jumping”:
jump(ing)?
This is an example of using some special characters that are available in regular expressions. The parenthesis create a group, and the question mark says “match the previous element (the group in this case) 0 or 1 times”.
Now for a really complex example. This one matches a pair of opening and closing HTML tags and the content in between.
<([a-z][a-z0-9]*)\b[^>]*>(.*?)\1>
Wow, looks complicated, eh? :] Don’t worry, you’ll be learning about all the special characters in this regular expression in the rest of this tutorial, and by the time you’re done you should be able to understand how this works! :]
If you want more details about the previous regular expression, check out this discussion for an explanation.
Note: in real-world usage you probably shouldn’t use regular expressions alone to parse HTML. Use a standard XML parser instead!
Overall Concepts
Before you go any further, it’s important to understand a few core concepts about regular expressions.
Literal characters are the simplest kind of regular expression. They’re similar to a “find” operation in a word processor or text editor. For example, the single-character regular expression t
will find all occurrences of the letter “t”, and the regular expression jump
will find all appearances of “jump”. Pretty straightforward!
Just like a programming language, there are some reserved characters in regular expression syntax, as follows:
- [
- ( and )
- \
- *
- +
- ?
- { and }
- ^
- $
- .
- | (pipe)
- /
These characters are used for advanced pattern matching. If you want to search for one of these characters, you need to escape it with a backslash. For example, to search for all periods in a block of text, the pattern is not .
but rather \.
.
Each environment, be it Python, Perl, Java, C#, Ruby or whatever, has special nuances in its implementation of regular expressions. And Swift is no exception!
Both Objective-C and Swift require you to escape special characters in literal strings (i.e., precede them by a backslash \
character). One such special character is the backslash itself! Since the patterns used to create a regular expression are also strings, this creates an added complication in that you need to escape the backslash character when working with String
and NSRegularExpression
.
That means the standard regular expression \.
will appear as \\.
in your Swift (or Objective-C) code.
To clarify the above concept in point form:
- The literal
"\\."
defines a string that looks like this: \. - The regular expression \. will then match a single period character.
Capturing parentheses are used to group part of a pattern. For example, 3 (pm|am)
would match the text “3 pm” as well as the text “3 am”. The pipe character here (|
) acts like an OR operator. You can include as many pipe characters in your regular expression as you would like. As an example, (Tom|Dick|Harry)
is a valid pattern that matches any of those three names.
Grouping with parentheses comes in handy when you need to optionally match a certain text string. Say you are looking for “November” in some text, but it’s possible the user abbreviated the month as “Nov”. You can define the pattern as Nov(ember)?
where the question mark after the capturing parentheses means that whatever is inside the parentheses is optional.
These parentheses are termed “capturing” because they capture the matched content and allow you reference it in other places in your regular expression.
As an example, assume you have the string “Say hi to Harry”. If you created a search-and-replace regular expression to replace any occurrences of (Tom|Dick|Harry)
with that guy $1
, the result would be “Say hi to that guy Harry”. The $1
allows you to reference the first captured group of the preceding rule.
Capturing and non-capturing groups are somewhat advanced topics. You’ll encounter examples of capturing and non-capturing groups later on in the tutorial.
Character classes represent a set of possible single-character matches. Character classes appear between square brackets ([
and ]
).
As an example, the regular expression t[aeiou]
will match “ta”, “te”, “ti”, “to”, or “tu”. You can have as many character possibilities inside the square brackets as you like, but remember that any single character in the set will match. [aeiou]
looks like five characters, but it actually means “a” or “e” or “i” or “o” or “u”.
You can also define a range in a character class if the characters appear consecutively. For example, to search for a number between 100 to 109, the pattern would be 10[0-9]
. This returns the same results as 10[0123456789]
, but using ranges makes your regular expressions much cleaner and easier to understand.
But character classes aren’t limited to numbers — you can do the same thing with characters. For instance, [a-f]
will match “a”, “b”, “c”, “d”, “e”, or “f”.
Character classes usually contain the characters you want to match, but what if you want to explicitly not match a character? You can also define negated character classes, which start with the ^ character. For example, the pattern t[^o]
will match any combination of “t” and one other character except for the single instance of “to”.
NSRegularExpressions Cheat Sheet
Regular expressions are a great example of a simple syntax that can end up with some very complicated arrangements! Even the best regular expression wranglers keep a cheat sheet handy for those odd corner cases.
To help you out, we have put together an official raywenderlich.com NSRegularExpression Cheat Sheet PDF for you! Please download it and check it out.
In addition, here’s an abbreviated form of the cheat sheet below with some additional explanations to get you started:
- . matches any character.
p.p
matches pop, pup, pmp, p@p, and so on. - \w matches any “word-like” character which includes the set of numbers, letters, and underscore, but does not match punctuation or other symbols.
hello\w
will match “hello_” and “hello9″ and “helloo” but not “hello!” - \d matches a numeric digit, which in most cases means
[0-9]
.\d\d?:\d\d
will match strings in time format, such as “9:30″ and “12:45″. - \b matches word boundary characters such as spaces and punctuation.
to\b
will match the “to” in “to the moon” and “to!”, but it will not match “tomorrow”.\b
is handy for “whole word” type matching. - \s matches whitespace characters such as spaces, tabs, and newlines.
hello\s
will match “hello ” in “Well, hello there!”. - ^ matches at the beginning of a line. Note that this particular
^
is different from^
inside of the square brackets! For example,^Hello
will match against the string “Hello there”, but not “He said Hello”. - $ matches at the end of a line. For example,
the end$
will match against “It was the end” but not “the end was near” - * matches the previous element 0 or more times.
12*3
will match 13, 123, 1223, 122223, and 1222222223 - + matches the previous element 1 or more times.
12+3
will match 123, 1223, 122223, 1222222223, but not 13. - Curly braces {} contain the minimum and maximum number of matches. For example,
10{1,2}1
will match both “101” and “1001” but not “10001” as the minimum number of matches is one and the maximum number of matches is two.He[Ll]{2,}o
will match “HeLLo” and “HellLLLllo” and any such silly variation of “hello” with lots of L’s, since the minimum number of matches is 2 but the maximum number of matches is not set — and therefore unlimited!
That’s enough to get you started!
It’s time to start experimenting with these examples yourself, as they’re all included in the Playground mentioned above.
Implementing Regex in iOS
Now that you know the basics, it’s time to use regular expressions in an app.
If you haven’t done so yet, download the starter project for this NSRegularExpression tutorial. Once you’ve downloaded it, open up the project in Xcode and run it.
The UI for the app is mostly complete, but the core functionality of the app relies on regular expressions, which it doesn’t have…yet! Your job in this tutorial is to add the required regular expressions into this app to make it shine.
A few sample screenshots demonstrating the content of the application are shown below:
The sample application covers two common use-cases with regular expressions:
- Performing text search: both highlighting and search & replace
- Validating user input
You’ll start by implementing the most straightforward use of regular expressions: text search.
/Search( and replace)?/
Here’s the basic overview of the search-and-replace functionality of the app:
- The search view controller,
SearchViewController
has a read-onlyUITextView
that contains an excerpt from Pride and Prejudice. - The navigation bar contains a search button that will present
SearchOptionsViewController
modally. - The user will then type some information into the field and tap “Search”.
- The app will then dismiss the search view and highlight all matches in the text view.
- If the user selected the “Replace” option in
SearchOptionsViewController
, the app will perform a search-and-replace for all matches in the text, instead of highlighting the results.
Note: Your app uses the NSAttributedString
property of UITextView to highlight the search results. You can read more about it in chapter 15 of iOS 6 by Tutorials, “What’s New with Attributed Strings”.
You could also implement the highlighting functionality using Text Kit. Be sure to check out the Text Kit Tutorial in Swift to find out more.
There’s also a “Bookmark” button that allows the user to highlight any date, time or location in the text. For simplicity’s sake, you won’t cover every possible format of date, time and location strings that can appear in your text. You’ll implement this highlighting functionality at the very end of the tutorial.
Your first step to getting the search functionality working is to turn standard strings representing regular expressions into NSRegularExpression
objects.
Open up SearchOptionsViewController.swift. SearchViewController
presents this view controller modally, and allows the user to enter their search (and optional replace) terms, as well as specifying whether the search should be case sensitive or match only whole words.
Take a look at the SearchOptions
struct at the top of the file. SearchOptions
is a simple struct that encapsulates the user’s search options. The code passes an instance of SearchOptions
back to SearchViewController. It would be good to be able to use this directly to construct an appropriate NSRegularExpression
. You can do this by adding a custom initializer to NSRegularExpression
with an extension.
Choose File > New > File… and choose Swift File. Name your file RegexHelpers.swift. Open up the new file, and add the following code:
extension NSRegularExpression { convenience init?(options: SearchOptions) { let searchString = options.searchString let isCaseSensitive = options.matchCase let isWholeWords = options.wholeWords let regexOption: NSRegularExpressionOptions = (isCaseSensitive) ? .allZeros : .CaseInsensitive let pattern = (isWholeWords) ? "\\b\(searchString)\\b" : searchString self.init(pattern: pattern, options: regexOption, error: nil) } } |
This code adds a convenience initializer to NSRegularExpression
. It uses the various settings within the passed in SearchOptions
instance to configure things correctly.
- Whenever the user requests a case-insensitive search, the regular expression uses the
.CaseInsensitive
NSRegularExpressionOptions
value. The default behavior ofNSRegularExpression
is to perform case-sensitive searches, but in this case you’re using the more user-friendly default of case-insensitive searches. - If the user requests a whole word search, then the app wraps the regular expression pattern in the
\b
character class. Recall that\b
is the word boundary character class, so putting\b
before and after the search pattern will turn it into a whole word search (that is, the pattern “\bcat\b” will match only the word “cat”, but not “catch”).
If for any reason it’s not possible to create the NSRegularExpression
, then the initializer will fail and return nil. Now that you have the NSRegularExpression
object, you can use it for matching text along with many other operations.
Open SearchViewController.swift, find searchForText
, and replace it with the code below:
func searchForText(searchText: String, replaceWith replacementText: String, inTextView textView: UITextView) { let beforeText = textView.text let range = NSMakeRange(0, countElements(beforeText)) if let regex = NSRegularExpression(options: self.searchOptions!) { let afterText = regex.stringByReplacingMatchesInString(beforeText, options: .allZeros, range: range, withTemplate: replacementText) textView.text = afterText } } |
First, this method captures the current text in the UITextView
and calculates the text length. It’s possible to apply a regular expression to just a subset of your text, which is why you need to specify the range. In this case, you’re just using the the entire length of the string which will result in the regular expression being exercised over all of your text.
The real magic happens in the call to stringByReplacingMatchesInString
. This method returns a new string without mutating the old string. Then the method sets the new string on the UITextView
so the user can see the results.
Still in SearchViewController, find highlightText
and replace it with the following:
func highlightText(searchText: String, inTextView textView: UITextView) { // 1 let attributedText = textView.attributedText.mutableCopy() as NSMutableAttributedString // 2 let attributedTextRange = NSMakeRange(0, attributedText.length) attributedText.removeAttribute(NSBackgroundColorAttributeName, range: attributedTextRange) // 3 if let regex = NSRegularExpression(options: self.searchOptions!) { let range = NSMakeRange(0, countElements(textView.text)) let matches = regex.matchesInString(textView.text, options: .allZeros, range: range) // 4 for match in matches as [NSTextCheckingResult] { let matchRange = match.range attributedText.addAttribute(NSBackgroundColorAttributeName, value: UIColor.yellowColor(), range: matchRange) } } // 5 textView.attributedText = attributedText.copy() as NSAttributedString } |
Here’s a step-by-step explanation of the above code:
- First, get a mutable copy of the textview’s
attributedText
. - Then create an
NSRange
for the entire length of the text, and remove any background color text attributes that already exist within it. - As with find and replace, next create a regular expression using your convenience initializer and fetch an array of all matches for the regular expression within the textview’s text.
- Loop through each match (casting them as
NSTextCheckingResult
objects), and add a yellow colour background attribute for each one. - Finally, update the
UITextView
with the highlighted results.
Build and run your app and try searching on some various words and groups of words! You’ll see the search term highlighted throughout your text, as shown in the image below:
Try searching for the word “the” using various options and see the effects. Notice, for example, that when using whole words, the ‘the’ in ‘them’ does not highlight.
Also, test out the search and replace functionality to see that your text strings are replaced as expected, and try both the ‘match case’ and ‘whole words’ options.
Highlighting and replacing text are both great. But how else can you effectively use regular expressions in your apps?
Data Validation
Many apps will have some kind of user input, such as a user entering their email address or phone number. You’ll want to perform some level of data validation on this user input, both to ensure data integrity and to inform the user if they’ve made a mistake entering their data.
Regular expressions are perfect for many kinds of data validation, since they are excellent at pattern matching.
There are two things you need to add to your app: the validation patterns themselves, and a mechanism to validate the user’s input with those patterns. To make things easy for the user, all of the validations in your app will be case-insensitive, so you can just use lower-case in your patterns.
As an exercise, try to come up with the regular expressions to validate the following text strings (don’t worry about case sensitivity):
- First name — should consist of standard English letters and between one and ten characters in length.
- Middle initial — should consist of a single English letter.
- Last name — should consist of standard English letters plus the apostrophe (for names such as O’Brian) and between two and ten characters in length.
- Date of birth – should be one of the following date formats: dd/mm/yyyy, dd-mm-yyyy, or dd.mm.yyyy, and fall between 1/1/1900 and 31/12/2099.
Of course, you can use the iRegex playground to try out your expressions as you develop them.
How did you do with coming up with the required regular expressions? If you’re stuck, just go back to the cheat sheet above and look for the bits that will help you in the scenarios above.
The spoiler below shows the regular expressions you’ll be using. But try to figure them out yourself first, and check your results before reading further!
Open up SignUpViewController.swift and replace viewDidLoad
with the following code:
override func viewDidLoad() { super.viewDidLoad() textFields = [ firstNameField, middleInitialField, lastNameField, dateOfBirthField ] let patterns = [ "^[a-z]{1,10}$", // First name "^[a-z]$", // Middle Initial "^[a-z']{2,10}$", // Last Name "^(0[1-9]|1[012])[-/.](0[1-9]|[12][0-9]|3[01])[-/.](19|20)\\d\\d$" ] // Date of Birth regexes = patterns.map { NSRegularExpression(pattern: $0, options: .CaseInsensitive, error: nil) } } |
This creates an array of the text fields in the view controller and an array of string patterns. It then uses Swift’s map
function to create an array of NSRegularExpression
objects, one for each pattern.
To create the regular expression to validate the first name, you first match from the beginning of the string, then you match a range of characters from a-z and then finally match the end of the string ensuring that it is between 1 to 10 characters in length.
The next two patterns, middle initial, and last name, follow the same logic. In case of the middle initial, you don’t need to specify the length — {1}
— since ^[a-z]$
matches on one character by default.
Note that you’re not worrying about case sensitivity here — you’ll take care of that when instantiating the regular expression.
For the date of birth, you have a little more work to do. You match on the start of the string, then for the month portion you have a capturing group to match one of 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11 or 12, followed by another capturing group to match either -
, /
or .
.
For the day portion, you then have another capturing group to match one of 01, 02, … 29, 30, or 31, followed by capturing group to match either -
, /
or .
Finally, there is a capturing group to match either 19 or 20, followed by any two numeric characters.
You can get very creative with regular expressions. There are other ways to solve the above problem, such as using \d
instead of [0-9]
. However, any solution is perfectly fine as long as it works!
Note: In real-world usage, you likely wouldn’t use a regular expression to verify a date (and certainly not to check it’s within a particular date range). Instead, you’d probably want to use an NSDateFormatter
to attempt to parse a date from a string, and then compare your parsed NSDate
against reference dates.
Now that you have the patterns, you need to validate the entered text in each of the text fields.
Still in SignUpViewController.swift, find validateTextField
and replace the implementation with the following:
func validateTextField(textField: UITextField) { let index = find(textFields, textField) if let regex = regexes[index!] { let text = textField.text.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) let range = NSMakeRange(0, countElements(text)) let matchRange = regex.rangeOfFirstMatchInString(text, options: .ReportProgress, range: range) let valid = matchRange.location != NSNotFound textField.textColor = (valid) ? UIColor.trueColor() : UIColor.falseColor() } } |
This is very similar to what you did in SearchViewController.swift. You start off by grabbing the relevant regex from the regexes
array, trimming any whitespace off the user’s input for the textfield, and then creating a range for the whole text.
To actually check for a match, the codes tests the result of rangeOfFirstMatchInString(_:options:range:)
. This is probably the most efficient way to check for a match, since this call exits early when it finds the first match. However, there are other alternatives such as numberOfMatchesInString(_:options:range:)
if you need to know the total number of matches.
Run the project now, click on the Contacts button at the bottom right, and try entering some information into the sign up form. When you complete each field, you should see its text turn green or red depending on whether or not it’s valid, as in the screenshot below:
The above code uses stringByTrimmingCharactersInSet
to remove leading or trailing spaces in the user’s input – otherwise the patterns above will fail to validate if any whitespace was present. This is a textbook use of stringByTrimmingCharactersInSet
, but seeing as how this is a regular expressions tutorial, it’s interesting to take a brief diversion and consider how you could tackle whitespace using more regular expressions instead.
Trimming Whitespace: An Interlude
In a case like this, you have two options: either update the patterns to account for leading and trailing spaces, or create and apply another pattern to take out leading and trailing spaces before applying those validation patterns. The second approach keeps the validation patterns simple and concise, and you can refactor it into its own method. All you need is a regex!
Can you think of a regular expression to find spaces at the start and end of a string? Try it yourself before checking the spoiler below:
Great. The pattern ^\s+
will find leading whitespace and \s+$
will find trailing whitespace. Now you have a regular expression that matches whitespace, it’s time to put it to use. At the very bottom of SignUpViewController.swift, outside the curly braces of the class definition, add the following code:
extension String { func stringByTrimmingLeadingAndTrailingWhitespace() -> String { let leadingAndTrailingWhitespacePattern = "(?:^\\s+)|(?:\\s+$)" if let regex = NSRegularExpression(pattern: leadingAndTrailingWhitespacePattern, options: .CaseInsensitive, error: nil) { let range = NSMakeRange(0, countElements(self)) let trimmedString = regex.stringByReplacingMatchesInString(self, options: .ReportProgress, range:range, withTemplate:"$1") return trimmedString } else { return self } } } |
Just as you did for NSRegularExpression
, you’re adding an instance method to String
here. Before we get into how the method works, change validateTextField
to use this new method by changing the line that trims whitespace:
let text = textField.text.stringByTrimmingLeadingAndTrailingWhitespace() |
The new string method uses the pattern from above to create a regular expression, and returns a new string with any regex matches replaced with $1
. But what does $1
mean?
When you have capture groups in a regular expression (denoted by (round brackets)
), you can use $
and a number to reference the contents of a group. Regular expression documentation refers to this as a backreference. The number specifies which capture group you’re referencing.
As an example, given the regular expression:
Customer ID: (\d{3})-(\d{4})
Matched against the following text:
Customer ID: 123-0089
Then the value of $1
would be 123, and the value of $2
would be 0089. Useful stuff!
Going back to the whitespace example, though – won’t $1
just replace the whitespace with itself — since it’s a captured group — which effectively does nothing?
In this case, the ?: inside the parentheses tells the regular expression engine to create a non-capturing group. This means that the matched text isn’t stored in a buffer as it normally would be.
Since the first capturing group is a non-capturing group, the engine doesn’t capture anything — and therefore it’s empty! Thus the engine ends up matching white spaces and replacing them with nothing, which effectively removes the leading and trailing spaces.
Of course, this is something of a contrived use of a capture value. You could in fact (and in real life certainly would!) just use an empty string, ""
, as your template value!
More on Capture Groups
As a more realistic example, suppose you wanted to alter all your customer ID’s in a file so that the 4-digit part came before the 3-digit part, you want extra space between the number groups, and two hyphens instead of one? More specifically, you’d like:
Bob ID: 143-5546 Ellen ID: 447-6091 Piper ID: 314-1596 Easy ID: 217-1828
To become:
Bob ID: 5546 -- 143 Ellen ID: 6091 -- 447 Piper ID: 1596 -- 314 Easy ID: 1828 -- 217
How would you do it? The spoiler below has the answer. But try it yourself first.
You can see the effects of this example at the end of the included playground.
Note that the regular expression here allows for an arbitrary amount of white space between the name and ID and also between “ID:” and the actual numerical ID.
If you are still struggling to make sense out of non-capturing, capturing and back referencing, try out the following different scenarios in the playground to see what the results are (hint: you can use the `replaceMatches` function):
- replace the whitespace pattern above with “(^\\s+)|(\\s+$)” and template with “BOO”
- replace the whitespace pattern above with “(?:^\\s+)|(\\s+$)” and template with “$1BOO”
- replace the whitespace pattern above with “(?:^\\s+)|(\\s+$)” and template with “$2BOO”
Handling Multiple Search Results
You haven’t yet implemented the Bookmark button found on the navigation bar. When the user taps on it, the app should highlight any date, time or location strings in the text.
Open up SearchViewController.swift in Xcode, and find the following implementation for the Bookmark bar button item:
//MARK: Underline dates, times, and locations @IBAction func underlineInterestingData(sender: AnyObject) { underlineAllDates() underlineAllTimes() underlineAllLocations() } |
The method above calls three other helper methods to underline dates, times and locations in the text. If you look at the implementation of each of the helper methods above, you will see they are empty!
First, fill out the implementations of each method. Replace them all with the following:
func underlineAllDates() { if let regex = NSRegularExpression.regularExpressionForDates() { let matches = matchesForRegularExpression(regex, inTextView: textView) highlightMatches(matches) } } func underlineAllTimes() { if let regex = NSRegularExpression.regularExpressionForTimes() { let matches = matchesForRegularExpression(regex, inTextView: textView) highlightMatches(matches) } } func underlineAllLocations() { if let regex = NSRegularExpression.regularExpressionForLocations() { let matches = matchesForRegularExpression(regex, inTextView: textView) highlightMatches(matches) } } |
Each of these methods calls a factory method on NSRegularExpression
to create the appropriate regular expression. These don’t exist yet, but it’s a convenient place to encapsulate this behavior. The methods then find matches, and call highlightMatches
to color and underline each string in the text. Check out its implementation if you’re interested to see how it works.
Now to populate those regular expression methods. Open up RegexHelpers.swift and add the following placeholders inside the NSRegularExpression
extension:
class func regularExpressionForDates() -> NSRegularExpression? { let pattern = " " return NSRegularExpression(pattern: pattern, options: .CaseInsensitive, error: nil) } class func regularExpressionForTimes() -> NSRegularExpression? { let pattern = " " return NSRegularExpression(pattern: pattern, options: .CaseInsensitive, error: nil) } class func regularExpressionForLocations() -> NSRegularExpression? { let pattern = " " return NSRegularExpression(pattern: pattern, options: .allZeros, error: nil) } |
Now it’s your job to complete these patterns! Here are the requirements:
Date Requirements:
- xx/xx/xx or xx.xx.xx or xx-xx-xx format. Day, month and year placement is not important since the code will just highlight them. Example: 10-05-12.
- Full or abbreviated month name (e.g. Jan or January, Feb or February, etc.), followed by a one or two digit number (e.g. x or xx). The day of the month can be ordinal (e.g. 1st, 2nd, 10th, 21st, etc.), followed by a comma as separator, and then a four-digit number (e.g. xxxx). There can be zero or more white spaces between the name of the month, day and year. Example: March 13th, 2001
Time requirements:
- Find simple times like “9am” or “11 pm”: One or two digits followed by zero or more white spaces, followed by either lowercase “am” or “pm”.
Location requirements:
- Any word at least one character long, immediately followed by a comma, followed by zero or more white spaces followed by any capitalized English letter combination that is exactly 2 characters long. For example “Boston, MA”.
You can use the playground to try these out. See if you can sketch out the needed regular expressions!
Here’s three sample patterns you can try. Replace the empty pattern for of regularExpressionForDates with the following:
let pattern = "(\\d{1,2}[-/.]\\d{1,2}[-/.]\\d{1,2})|(Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sep(tember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?)\\s*(\\d{1,2}(st|nd|rd|th)?+)?[,]\\s*\\d{4}" |
This pattern has two parts separated by the |
(OR) character. That means either the first part or the second part will match.
The first part reads: (\d{1,2}[-/.]\d{1,2}[-/.]\d{1,2})
. That means two digits followed by one of -
or /
or .
followed by two digits, followed by -
or /
or .
, followed by a final two digits.
The second part starts with (Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sep(tember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?)
, which will match a full or abbreviated month name.
Next up is \\s*\\d{1,2}(st|nd|rd|th)?
which will match zero or more spaces, followed by 1 or 2 digits, followed by an optional ordinal suffix. As an example, this will match both “1” and “1st”.
Finally [,]\\s*\\d{4}
will match a comma followed by zero or multiple spaces followed by a four-digit number for the year.
That’s quite the intimidating regular expression! However, you can see how regular expressions are concise and pack a lot of information — and power! — into a seemingly cryptic string.
Next up are the the patterns for regularExpressionForTimes
and regularExpressionForLocations
. Fill in the blank patterns with the following:
// Times let pattern = "\\d{1,2}\\s*(pm|am)" // Locations let pattern = "[a-zA-Z]+[,]\\s*([A-Z]{2})" |
As an exercise, see if you can explain the regular expression patterns based on the specifications above.
Build and run the app and tap on the Bookmark icon. You should see the link-style highlighting for dates, times, and locations, as shown below:
While it’s fine for this example, can you see why the regular expression for time might not be right for more general searching? As it stands it would not match the time 3:15pm and it would match 28pm.
Here’s a challenging problem! Figure out how to rewrite the regular expression for time so that it matches a more a general time format.
Specifically, your answer should match times in the format ab:cd am/pm for a standard 12-hour clock. So it should match: 11:45 am, 10:33pm, 04:12am but not 2pm, 0:00am 18:44am 9:63pm or 7:4 am. There should be at most one space before the am/pm. By the way, it’s acceptable if it matches the 4:33am in 14:33am.
One possible answer appears below, but try it yourself first. Check the end of the accompanying playground to see it in action.
Where To Go From Here?
Here is the final example project that you developed in the above tutorial.
Congratulations! You now have some practical experience with using regular expressions.
Regular expressions are powerful and fun to work with — they’re a lot like solving a math problem. The flexibility of regular expressions gives you many ways to create a pattern to fit your needs, such as filtering input strings for white spaces, stripping out HTML or XML tags before parsing, or finding particular XML or HTML tags — and much more!
There are a lot of real-world examples of strings that you can validate with regular expressions.
As a final exercise, try to untangle the following regular expression that validates an email address:
[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])? |
It looks like a jumble of characters at first glance, but with your new-found knowledge (and the helpful links below) you’re one step closer to understanding it and becoming a master of regular expressions!
Here is a short list of some useful resources about regular expressions:
- www.regular-expressions.info is an informative site by Jan Goyvaerts. He also has published some very comprehensive books on regular expressions.
- NSRegularExpression Class Reference is always your best reference for using NSRegularExpression API.
- For a quick test of a regex pattern, regexpal.com is very handy resource.
And in case you missed the links earlier, check out these resources we’ve prepared for you:
I hope you enjoyed this NSRegularExpression tutorial, and if you have any comments or questions, please join the forum discussion below!
NSRegularExpression in Swift Tutorial is a post from: Ray Wenderlich
The post NSRegularExpression in Swift Tutorial appeared first on Ray Wenderlich.