Regular Expressions Tutorial: Getting Started

In this tutorial, you’ll learn how to implement regular expressions in an iOS app using Swift 4.2. By Tom Elliott.

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

Handling Multiple Search Results

You haven’t used the Reading Mode button found on the Navigation bar yet. When the user taps on it, the app should enter a ‘focused’ mode, highlighting any date or time strings in the text, as well as highlighting the end of every diary entry.

Open SearchViewController.swift in Xcode, and find the following implementation for the Reading Mode button item:

//MARK: Underline dates, times, and splitters

@IBAction func toggleReadingMode(_ sender: AnyObject) {
  if !self.readingModeEnabled {
    readingModeEnabled = true
    decorateAllDatesWith(.underlining)
    decorateAllTimesWith(.underlining)
    decorateAllSplittersWith(.underlining)
  } else {
    readingModeEnabled = false
    decorateAllDatesWith(.noDecoration)
    decorateAllTimesWith(.noDecoration)
    decorateAllSplittersWith(.noDecoration)
  }
}

The method above calls three other helper methods to decorate dates, times and diary entry splitters in the text. Each method takes a decoration option for either underlining the text or setting no decoration (removing the underlining). If you look at the implementation of each of the helper methods above, you will see they are empty!

Before you worry about implementing the decoration methods, you should define and create the NSRegularExpressions themselves. A convenient way to do this is to create static variables on NSRegularExpression. Switch to RegexHelpers.swift and add the following placeholders inside the NSRegularExpression extension:

static var regularExpressionForDates: NSRegularExpression? {
  let pattern = ""
  return try? NSRegularExpression(pattern: pattern, options: .caseInsensitive)
}

static var regularExpressionForTimes: NSRegularExpression? {
  let pattern = ""
  return try? NSRegularExpression(pattern: pattern, options: .caseInsensitive)
}

static var regularExpressionForSplitter: NSRegularExpression? {
  let pattern = ""
  return try? NSRegularExpression(pattern: pattern, options: [])
}

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 1 or 2 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 space 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”.

Splitter Requirements:

  • A sequence of tilde (~) characters at least 10 long.

You can use the playground to try these out. See if you can figure out the needed regular expressions!

Here are three sample patterns you can try. Replace the empty pattern for of regularExpressionForDates with the following:

(\\d{1,2}[-/.]\\d{1,2}[-/.]\\d{1,2})|((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)((r)?uary|(tem|o|em)?ber|ch|il|e|y|)?)\\s*(\\d{1,2}(st|nd|rd|th)?+)?[,]\\s*\\d{4}

This pattern has two parts separated by the | (OR) character. That means that 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|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)((r)?uary|(tem|o|em)?ber|ch|il|e|y|)?), 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 one or two 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 regularExpressionForSplitters. Fill in the blank patterns with the following:

// Times
\\d{1,2}\\s*(pm|am)

// Splitters
~{10,}

As an exercise, see if you can explain the regular expression patterns based on the specifications above.

Finally, open SearchViewController.swift and fill out the implementations of each decoration method in SearchViewController as follows:

func decorateAllDatesWith(_ decoration: Decoration) {
  if let regex = NSRegularExpression.regularExpressionForDates {
    let matches = matchesForRegularExpression(regex, inTextView: textView)
    switch decoration {
    case .underlining:
      highlightMatches(matches)
    case .noDecoration:
      removeHighlightedMatches(matches)
    }
  }
}

func decorateAllTimesWith(_ decoration: Decoration) {
  if let regex = NSRegularExpression.regularExpressionForTimes {
    let matches = matchesForRegularExpression(regex, inTextView: textView)
    switch decoration {
    case .underlining:
      highlightMatches(matches)
    case .noDecoration:
      removeHighlightedMatches(matches)
    }
  }
}

func decorateAllSplittersWith(_ decoration: Decoration) {
  if let regex = NSRegularExpression.regularExpressionForSplitter {
    let matches = matchesForRegularExpression(regex, inTextView: textView)
    switch decoration  {
    case .underlining:
      highlightMatches(matches)
    case .noDecoration:
      removeHighlightedMatches(matches)
    }
  }
}

Each of these methods uses one of the static variables on NSRegularExpression to create the appropriate regular expression. They then find matches and call highlightMatches(_:) to color and underline each string in the text or removeHighlightedMatches(_:) to revert the style changes. Check out their implementations if you’re interested to see how they work.

Build and run the app. Now, tap on the Reading Mode icon. You should see the link-style highlighting for dates, times and splitters, as shown below:

iRegex Date, Time, and Location Highlighting

Tap the button again to disable reading mode, and return the text to its normal style.

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.

[spoiler]

"(1[0-2]|0?[1-9]):([0-5][0-9]\\s?(am|pm))"

[/spoiler]

Where to Go From Here?

Congratulations! You now have some practical experience with using regular expressions.

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

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!