Regular Expressions Tutorial: Getting Started | Ray Wenderlich

This introduction to regular expressions teaches you the basics of regular expressions and how to use them.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/5765-regular-expressions-tutorial-getting-started

The starter project would not compile out of the box.
XCode 9.4.1
macOS 10.13.6 (High Sierra)

Here’s a diff that made it happy:

--- [SOURCE ROOT]/iRegex/iRegex_StarterProject/iRegex/LoginViewController.swift
+++ [SOURCE ROOT]/iRegex/iRegex_StarterProject/iRegex/LoginViewController.swift
@@ -42,8 +42,8 @@

override func viewDidLoad() {
let notificationCenter = NotificationCenter.default
-    notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
-    notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
+    notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: .UIKeyboardWillHide, object: nil)
+    notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: .UIKeyboardWillChangeFrame, object: nil)

passwordField.becomeFirstResponder()

@@ -65,10 +65,10 @@
@objc func adjustForKeyboard(notification: Notification) {
let userInfo = notification.userInfo!

-    let keyboardScreenEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
+    let keyboardScreenEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)

-    if notification.name == UIResponder.keyboardWillHideNotification {
+    if notification.name == Notification.Name.UIKeyboardWillHide {
scrollView.contentInset = .zero
} else {
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height + 72, right: 0)


--- [SOURCE ROOT]/iRegex/iRegex_StarterProject/iRegex/SearchViewController.swift
+++ [SOUCE ROOT]/iRegex/iRegex_StarterProject/iRegex/SearchViewController.swift
@@ -122,7 +122,7 @@
let attributedText = textView.attributedText.mutableCopy() as! NSMutableAttributedString
for match in matches {
let matchRange = match.range
-      attributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.blue, range: matchRange)
+      attributedText.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.blue, range: matchRange)
}
textView.attributedText = (attributedText.copy() as! NSAttributedString)
}
@@ -132,7 +132,7 @@

for match in matches {
let matchRange = match.range
-      attributedText.removeAttribute(NSAttributedString.Key.foregroundColor, range: matchRange)
+      attributedText.removeAttribute(NSAttributedStringKey.foregroundColor, range: matchRange)
}
textView.attributedText = (attributedText.copy() as! NSAttributedString)
}

…or get XCode 10 :thinking::exploding_head:

1 Like

Thanks for adding this, hopefully it’ll be useful for others :]

Hi! The documentation at Apple Developer Documentation
mentions that the flag values (options) may be specified within the pattern using the (?ismx-ismx) pattern options.
I could not find an example of how to write these options in the pattern instead of using the options parameter.
How does one write the flags into the pattern?

@tomelliott Can you please help with this when you get a chance? Thank you - much appreciated! :]

Hi @nunop, you specify the ismwx options within parenthesis, like this:

"Hello (?i)world" 

In this instance, even if your regular expression didn’t have the NSRegularExpression.Options.caseInsensitive option set, “world” would be matched with any capitalisation.

Here’s some example code you can copy into a Playground:

import Foundation

// Here, we have a regular expression which is case sensitive. Consequently we match only the final, all lowercase hello and the final, all lowercase world, for 2 total matches
if let regex = try? NSRegularExpression(pattern: "hello|world", options: []) {
  let haystack = "Hello HELLO hello World WORLD world"
  let range = NSMakeRange(0, haystack.count)
  let matches = regex.matches(in: haystack, options: [], range: range)

  print("Found \(matches.count) match(es)")
} else {
  print("Invalid pattern")
}

// Here, we have the same pattern, but we've made the entire thing case insensitive, so we match all three hellos and all three worlds, for 6 total matches.
if let regex = try? NSRegularExpression(pattern: "hello|world", options: [.caseInsensitive]) {
  let haystack = "Hello HELLO hello World WORLD world"
  let range = NSMakeRange(0, haystack.count)
  let matches = regex.matches(in: haystack, options: [], range: range)

  print("Found \(matches.count) match(es)")
} else {
  print("Invalid pattern")
}

// Finally, we have the same pattern but using the ?ismwx flags, which apply to only a part of a pattern. So in this case, we set the second part of the pattern to be case insensitive matching world with any case, but keep hello case sensitive, for 4 total matches
if let regex = try? NSRegularExpression(pattern: "hello|(?i)world", options: []) {
  let haystack = "Hello HELLO hello World WORLD world"
  let range = NSMakeRange(0, haystack.count)
  let matches = regex.matches(in: haystack, options: [], range: range)

  print("Found \(matches.count) match(es)")
} else {
  print("Invalid pattern")
}

I’ve just implemented the first part of the tutorial where I can search for text and see any matches highlighted in yellow. I searched for eee and expected all the e’s in eeeeeeeevil to be highlighted but only the first six are, i.e. eeeeeeeevil. I don’t understand why the last highlighted e along with the two unhighlighted e’s aren’t a match as well. All the e’s are highlighted if I search for ee or eeee.

Hi @magnas. This is as expected. When the regular expression matches a range in the string the Regex engine moves on to the following character after the end of the match and not the next character after the beginning of the match.

You can see this clearly using the following regex testing site (which isn’t Swift specific) , which highlights each matched range independently (or by breakpointing in Xcode and inspecting the range for each match. I added breakpoints at lines 109 and 111 in SearchViewController)

2 e’s

(and from the debugger)

(lldb) po matches

▿ 14 elements

- 0 : <NSSimpleRegularExpressionCheckingResult: 0x600002525780>{101, 2}{<NSRegularExpression: 0x600003e3b2d0> ee 0x0}

- 1 : <NSSimpleRegularExpressionCheckingResult: 0x600002525740>{103, 2}{<NSRegularExpression: 0x600003e3b2d0> ee 0x0}

- 2 : <NSSimpleRegularExpressionCheckingResult: 0x600002525700>{105, 2}{<NSRegularExpression: 0x600003e3b2d0> ee 0x0}

- 3 : <NSSimpleRegularExpressionCheckingResult: 0x600002525600>{107, 2}{<NSRegularExpression: 0x600003e3b2d0> ee 0x0}

...  (plus 10 more)

(lldb) po matchRange

▿ {101, 2}

- location : 101

- length : 2

(lldb) po matchRange

▿ {103, 2}

- location : 103

- length : 2

(lldb) po matchRange

▿ {105, 2}

- location : 105

- length : 2

(lldb) po matchRange

▿ {107, 2}

- location : 107

- length : 2

3 e’s

(and from the debbugger)

(lldb) po matches

▿ 2 elements

- 0 : <NSSimpleRegularExpressionCheckingResult: 0x6000025532c0>{101, 3}{<NSRegularExpression: 0x600003e303f0> eee 0x0}

- 1 : <NSSimpleRegularExpressionCheckingResult: 0x600002550b80>{104, 3}{<NSRegularExpression: 0x600003e303f0> eee 0x0}

(lldb) po matchRange

▿ {101, 3}

- location : 101

- length : 3

(lldb) po matchRange

▿ {104, 3}

- location : 104

- length : 3

Ah now it’s clear. Many thanks, Tom.

1 Like

Hi Tom, Thanks for the tutorial. Currently, in my project, I have to detect the characters outside of this set. [^a-zA-Z0-9 !@#$^&*_+=-~/?.:'‘’].

So far, I’m not able to include the square brackets “[” and “]” into the set. For “-” minus sign, I can using 2 “\” forward slashes.

I tried working on regexr.com. It worked there, but when I copied to my Xcode swift program, it doesn’t.

Thanks.

Tim

@thinh_le Please let us know what regular expression are you using exactly. Thank you!

Hi Tim.

Escaping square brackets is the same as escaping other reserved characters, you just need a two slashes in your pattern, like this:

"\\["

If that’s not working for you then you likely have a different underlying issue. As @shogunkaramazov mentions if you can provide the exact code you’re using I’ll see what I can do!

oh, I bet this is because you’ve not escaped the dash properly. This means the regex engine will be treating =-~ as a character range between = (61 in ASCII) and ~ (126 in ASCII), which is basically everything you care about other than digits.

If I’ve understood correctly, use the following string as the pattern input for your regular expression and you should be all good:

"[^a-zA-Z0-9 \\[\\]!@#$^&*_+=\\-~/?.:'\"]"

Hi Tom, it’s working with just “\\]” . Thanks.

As for the “[”, I tried in www.regexr.com, it works as is without any escape character. But when I inserted to my Swift program, it allows “[” as well as other unwanted characters. So I used “\\[” as you suggested.

As for the “=-~”, our code is actually “=\\-~”, which allows the minus sign in our set.

So, it is good like this, as I don’t need the double quote.

“[^a-zA-Z0-9 \\[\\]!@#$^&*_+=\\-~/?.:'()]”

Thanks Tom for the quick reply.

1 Like

Thanks also to Shogunkaramazov !

This tutorial is more than six months old so questions are no longer supported at the moment for it. Thank you!