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.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

Updating Searches While Scrolling

You need a mechanism to update the UI before they see that. To do this, you’ll implement two UIScrollView delegate methods to update the view as the user scrolls.

Create the following two methods in RWFirstViewController.m:

// Called when the user finishes scrolling the content
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    if (CGPointEqualToPoint(velocity, CGPointZero))
    {
        if (self.lastSearchString && self.lastSearchOptions && !self.lastReplacementString)
            [self searchText:self.lastSearchString inTextView:self.textView options:self.lastSearchOptions];
    }
}

// Called when the scroll view has ended decelerating the scrolling movement
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    if (self.lastSearchString && self.lastSearchOptions && !self.lastReplacementString)
        [self searchText:self.lastSearchString inTextView:self.textView options:self.lastSearchOptions];
}

Now every time the user scrolls, you perform a fresh search and update the UI accordingly.

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:

regex-search

Scroll through the text and see that your UI updates are keeping pace. As well, test out the search and replace functionality to see that your text strings are replaced as expected.

Highlighting and replacing are great user-facing functions. But what about for you, the programmer? How can regular expressions be used to your advantage 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 this kind of data validation, since they are excellent at parsing string patterns.

There are two thing 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 insensitivity):

  • First name — should be composed of standard English letters and between one and ten characters in length.
  • Middle initial — should be composed of standard English letters and be only one character in length.
  • Last name — should be composed of standard English letters plus the apostrophe (for names such as O’Brien) and between two and ten characters in length.
  • Date of birth – should fall between 1/1/1900 and 12/31/2099, and should be one of the following date formats: dd/mm/yyyy, dd-mm-yyyy, or dd.mm.yyyy.

Of course, you can use regexpal 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 in code, and where to add them in your code — but try it yourself first, without looking!

[spoiler]
Replace self.validations = nil; in RWSecondViewController.m with the following code:

// Array of regex to validate each field
self.validations = @[@"^[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
                     ];

[/spoiler]

How did you do?

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.

Note that you’re not worrying about case insensitivity 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!

Now that you have the patterns, you need to validate the entered text in each text fields.

At the very end of RWSecondViewController.m, find the validateString:withPattern: method and replace the implementation with the following:

// Validate the input string with the given pattern and
// return the result as a boolean
- (BOOL)validateString:(NSString *)string withPattern:(NSString *)pattern
{
    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];
    
    NSAssert(regex, @"Unable to create regular expression");
    
    NSRange textRange = NSMakeRange(0, string.length);
    NSRange matchRange = [regex rangeOfFirstMatchInString:string options:NSMatchingReportProgress range:textRange];
    
    BOOL didValidate = NO;
    
    // Did we find a matching range
    if (matchRange.location != NSNotFound)
        didValidate = YES;
    
    return didValidate;
}

This is very similar to what you did in RWFirstViewController.m. You create a regular expression with the given pattern, and since case sensitivity is not required, you use NSRegularExpressionCaseInsensitive.

To actually check for a match, the result of rangeOfFirstMatchInString:options:range: is tested. 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.

There’s a small problem with the patterns above. Did you notice what it was?

If the user provides leading or trailing spaces in their fields, then the patterns above will fail to validate. 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 starter project uses the second approach since it keeps the validation patterns simple and concise, and can be refactored into its own method. This is done in stringTrimmedForLeadingAndTrailingWhiteFromString:.

regex-whitespace

Take a look at the following method in RWSecondViewController.m:

// Trim the input string by removing leading and trailing white spaces
// and return the result
- (NSString *)stringTrimmedForLeadingAndTrailingWhiteFromString:(NSString *)string
{
    return string;
}

Right now, this method doesn’t do anything — it simply returns the input.

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:

[spoiler]

(?:^\\s+)|(?:\\s+$)

[/spoiler]

Here’s how to put it into code. In RWSecondViewController.m, replace the implementation of stringTrimmedForLeadingAndTrailingWhiteSpacesFromString with the following:

// Trim the input string by removing leading and trailing white spaces
// and return the result
- (NSString *)stringTrimmedForLeadingAndTrailingWhiteSpacesFromString:(NSString *)string
{
    NSString *leadingTrailingWhiteSpacesPattern = @"(?:^\\s+)|(?:\\s+$)";
    
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:leadingTrailingWhiteSpacesPattern options:NSRegularExpressionCaseInsensitive error:NULL];
    
    NSRange stringRange = NSMakeRange(0, string.length);
    NSString *trimmedString = [regex stringByReplacingMatchesInString:string options:NSMatchingReportProgress range:stringRange withTemplate:@"$1"];
    
    return trimmedString;
}

The pattern ^\s+ will find leading whitespace and \s+$ will find trailing whitespace.

That’s clear enough, and the call to stringByReplacingMatchesInString:options:range:withTemplate: is the same as in RWFirstViewController.m.

But why is the replacement string $1? Won’t that just replace the whitespace with itself — since it’s a captured group — which effectively does nothing?

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.

The replacement template here is a back-reference to the first capturing group: $1. It tells the engine to replace the matched text with what what was matched in the first capturing group.

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.

Build and run your app and switch to the second tab. As you fill in the fields, the regular expressions will check what you entered, and your app will inform you if your text passed or failed validation, as shown in the screenshot below:

regex-second

If you are still struggling to make sense out of non-capturing, capturing and back referencing, try out the following different scenarios to see what the results are.For example:

  • replace the pattern above with @”(^\\s+)|(\\s+$)” and template with @”BOO”
  • replace the pattern above with @”(?:^\\s+)|(\\s+$)” and template with @”$1BOO”
  • replace the pattern above with @”(?:^\\s+)|(\\s+$)” and template with @”$2BOO”

Contributors

Over 300 content creators. Join our team.