Regular expressions can be used to extract structured data from expected formats using capture groups. This post presents examples of using named and unnamed regular expression capture groups in Swift:

  1. Capture Groups
    a. Create A NSRegularExpression
    b. Find Capture Groups
    c. Extract Capture Groups
  2. Named Capture Groups
    a. Create A NSRegularExpression With Named Capture Groups
    b. Find Named Capture Groups
    c. Extract Named Capture Groups

Capture Groups

Capture groups are a feature of regular expressions that allow data to be extracted from structured formats. For example, in the string "firstName middleName lastName" the substring "firstName" can be extracted using a capture group.

When creating a regular expression, use parentheses to form a capture group. In the following example, the first capture group is ([a-zA-Z-]+) which matches one or more lowercase or uppercase letters.

Create A NSRegularExpression

let name = "firstName middleName lastName"
let nameRange = NSRange(
    name.startIndex..<name.endIndex,
    in: name
)

// Create A NSRegularExpression
let capturePattern = #"([a-zA-Z-]+) ?.* ([a-zA-Z-]+)?"#
let captureRegex = try! NSRegularExpression(
    pattern: capturePattern,
    options: []
)

Find Capture Groups

// Find the matching capture groups
let matches = captureRegex.matches(
    in: name,
    options: [],
    range: nameRange
)

guard let match = matches.first else {
    // Handle exception
    throw NSError(domain: "", code: 0, userInfo: nil)
}

Extract Capture Groups

Capture groups are available as ranges on a NSTextCheckingResult. Use numberOfRanges to determine the number of matches and range(at:) to get the NSRange corresponding to the capture group at a specific index.

var names: [String] = []

// For each matched range, extract the capture group
for rangeIndex in 0..<match.numberOfRanges {
    let matchRange = match.range(at: rangeIndex)
    
    // Ignore matching the entire username string
    if matchRange == nameRange { continue }
    
    // Extract the substring matching the capture group
    if let substringRange = Range(matchRange, in: name) {
        let capture = String(name[substringRange])
        names.append(capture)
    }
}

names.first // firstName
names.last // lastName

Named Capture Groups

Named capture groups enable access to capture groups using a name, instead of a range index. Named capture groups may improve code readability for complex regular expressions and regular expressions with multiple capture groups.

Create A NSRegularExpression With Named Capture Groups

let birthday = "01/02/2003"
let birthdayRange = NSRange(
    birthday.startIndex..<birthday.endIndex,
    in: birthday
)

// Create A NSRegularExpression
let capturePattern = 
    #"(?<month>\d{1,2})\/"# + 
    #"(?<day>\d{1,2})\/"# + 
    #"(?<year>\d{1,4})"#

let birthdayRegex = try! NSRegularExpression(
    pattern: capturePattern,
    options: []
)

Find Named Capture Groups

// Find the matching capture groups
let matches = birthdayRegex.matches(
    in: birthday,
    options: [],
    range: birthdayRange
)

guard let match = matches.first else {
    // Handle exception
    throw NSError(domain: "", code: 0, userInfo: nil)
}

Extract Named Capture Groups

To get the NSRange associated with a named capture group, pass the name of the capture group to range(withName:) on a NSTextCheckingResult.

var captures: [String: String] = [:]

// For each matched range, extract the named capture group
for name in ["month", "day", "year"] {
    let matchRange = match.range(withName: name)
    
    // Extract the substring matching the named capture group
    if let substringRange = Range(matchRange, in: birthday) {
        let capture = String(birthday[substringRange])
        captures[name] = capture
    }
}

captures["month"] // 01
captures["day"] // 02
captures["year"] // 2003

Capture Groups in Swift

That’s it! By using NSRegularExpression you can extract data using regular expression capture groups in Swift.