NSRegularExpression Tutorial and Cheat Sheet
A NSRegularExpression tutorial that shows you how to search, replace, and validate data in your app. Includes a handy NSRegularExpression cheat sheet PDF! By Soheil Azarpour.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
NSRegularExpression Tutorial and Cheat Sheet
45 mins
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_9” 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 is1
and the maximum number of matches is2
.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!
If you would like more background on regular expressions, hit up your favourite search engine to find some online regular expression references. If you’re more of a dead-tree type of person, there’s also a great number of thick paper books that too explain all the details of regular expressions.
Or – you can just load up regexpal again and start experimenting with some of the syntax you’ve learned so far!
Implementing RegEx in iOS
Now that you know the basics, it’s time to get into iOS-specific implementations of regular expressions.
Start by downloading 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 three common use-cases with regular expressions:
- Performing text search, as well as search & replace
- Validating user input
- Auto-formatting 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 first view controller
RWFirstViewController
has a read-only UITextView that contains some pre-filled content. - The navigation bar contains a search button that will present
RWSearchViewController
in a modal fashion. - 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
RWSearchViewController
, 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 in iOS 6 to highlight the search results. You can read more about it in iOS 6 by Tutorials – Chapter 15, “What’s New with Attributed Strings”.
Note: Your app uses the NSAttributedString
property of UITextView in iOS 6 to highlight the search results. You can read more about it in iOS 6 by Tutorials – Chapter 15, “What’s New with Attributed Strings”.
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 not cover every possible format of date, time and location strings that can appear in your text. You’ll implement the bookmarking 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 RWFirstViewController.m and replace the stub implementation of regularExpressionWithString:options:
with the following:
// Create a regular expression with given string and options
- (NSRegularExpression *)regularExpressionWithString:(NSString *)string options:(NSDictionary *)options
{
// Create a regular expression
BOOL isCaseSensitive = [[options objectForKey:kRWSearchCaseSensitiveKey] boolValue];
BOOL isWholeWords = [[options objectForKey:kRWSearchWholeWordsKey] boolValue];
NSError *error = NULL;
NSRegularExpressionOptions regexOptions = isCaseSensitive ? 0 : NSRegularExpressionCaseInsensitive;
NSString *placeholder = isWholeWords ? @"\\b%@\\b" : @"%@";
NSString *pattern = [NSString stringWithFormat:placeholder, string];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:regexOptions error:&error];
if (error)
{
NSLog(@"Couldn't create regex with given string and options");
}
return regex;
}
The workhorse in this method is the call to regularExpressionWithPattern:options:error
. This turns a string into a NSRegularExpression object.
However, before that happens, there are two things to consider:
- If a case-insensitive search is requested, then set the value of
NSRegularExpressionCaseInsensitive
. The default behavior ofNSRegularExpression
is to perform case-sensitive searches, but in this case you’re using the user-friendly option of case-insensitive searches. - If a whole word search is requested, then wrap 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.
Now that you have the NSRegularExpression
object, you can use it for matching text along with many other operations.
Inside RWFirstViewController.m, find searchAndReplaceText
and modify it as follows:
// Search for a searchString and replace it with the replacementString in the given text view with search options
- (void)searchAndReplaceText:(NSString *)searchString withText:(NSString *)replacementString inTextView:(UITextView *)textView options:(NSDictionary *)options
{
// Text before replacement
NSString *beforeText = textView.text;
// Create a range for it. We do the replacement on the whole
// range of the text view, not only a portion of it.
NSRange range = NSMakeRange(0, beforeText.length);
// Call the convenient method to create a regex for us with the options we have
NSRegularExpression *regex = [self regularExpressionWithString:searchString options:options];
// Call the NSRegularExpression method to do the replacement for us
NSString *afterText = [regex stringByReplacingMatchesInString:beforeText options:0 range:range withTemplate:replacementString];
// Update UI
textView.text = afterText;
}
First, this method captures the old text in the UITTextView
and the text length is calculated. 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 stringByReplacingMatchesInString:options:range:withTemplate
. Here, it returns a new string without mutating the old string. This new string is then set on the UITextView
so the user can see the results.
Add the following method to RWFirstViewController.m:
// Search for a searchString in the given text view with search options
- (void)searchText:(NSString *)searchString inTextView:(UITextView *)textView options:(NSDictionary *)options
{
// 1: Range of visible text
NSRange visibleRange = [self visibleRangeOfTextView:self.textView];
// 2: Get a mutable sub-range of attributed string of the text view that is visible
NSMutableAttributedString *visibleAttributedText = [textView.attributedText attributedSubstringFromRange:visibleRange].mutableCopy;
// Get the string of the attributed text
NSString *visibleText = visibleAttributedText.string;
// 3: Create a new range for the visible text. This is different
// from visibleRange. VisibleRange is a portion of all textView that is visible, but
// visibileTextRange is only for visibleText, so it starts at 0 and its length is
// the length of visibleText
NSRange visibleTextRange = NSMakeRange(0, visibleText.length);
// 4: Call the convenient method to create a regex for us with the options we have
NSRegularExpression *regex = [self regularExpressionWithString:searchString options:options];
// 5: Find matches
NSArray *matches = [regex matchesInString:visibleText options:NSMatchingProgress range:visibleTextRange];
// 6: Iterate through the matches and highlight them
for (NSTextCheckingResult *match in matches)
{
NSRange matchRange = match.range;
[visibleAttributedText addAttribute:NSBackgroundColorAttributeName value:[UIColor yellowColor] range:matchRange];
}
// 7: Replace the range of the attributed string that we just highlighted
// First, create a CFRange from the NSRange of the visible range
CFRange visibleRange_CF = CFRangeMake(visibleRange.location, visibleRange.length);
// Get a mutable copy of the attributed text of the text view
NSMutableAttributedString *textViewAttributedString = self.textView.attributedText.mutableCopy;
// Replace the visible range
CFAttributedStringReplaceAttributedString((__bridge CFMutableAttributedStringRef)(textViewAttributedString), visibleRange_CF, (__bridge CFAttributedStringRef)(visibleAttributedText));
// 8: Update UI
textView.attributedText = textViewAttributedString;
}
Here’s a step-by-step explanation of the above code:
- In order to be as efficient as possible, get the range of the textview that is visible on the screen. You only need to search and highlight what’s visible on the screen — not the text that is off-screen. The helper method
visibleRangeOfTextView:
returns anNSRange
of the currently visible text in aUITextView
. - Get a mutable sub-range of attributed string of the visible portion of the textview that you can use in the next step.
- You now create a new
NSRange
for the visible text. This is different fromvisibleRange
.visibleRange
determines which portion of all textView that is visible, but visibileTextRange is only for the substring of attributed text you pulled out, so it should start at 0 and its length is the length of visibleText. This is the range you will pass into the regex engine. - Call the convenience method
regularExpressionWithString
to create a regex for you with the provided options. - Find matches and store them in an array. These matches are all instances of
NSTextCheckingResult
. - Iterate through the matches and highlight them.
- Replace the range of the attributed string that you just highlighted. To do this, create a
CFRange
from theNSRange
of the visible range. Get a mutable copy of the attributed text of the text view and perform the replace. - Update the UITextView with the highlighted results.
While searching, highlighting, and rendering attributed strings is a pretty cool feature to have in your app, it comes at a performance cost — especially if your text is very long.
To handle this in the most efficient way possible, you perform the find-and-highlight on only the visible portion of the text view.
However, when your user scrolls the text they’ll see results that aren’t highlighted. Whoops!