Swift
regex
programming
coding
tutorial

Swift extract regex matches

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Extracting regex matches in Swift usually means two separate tasks: finding the text that matches a pattern and converting Foundation ranges back into native Swift string ranges safely. The mechanics are straightforward once you know where the friction comes from. Most problems come from NSRange versus String.Index, not from the regex pattern itself.

Use NSRegularExpression for broad compatibility

NSRegularExpression is still the most compatible choice across existing Swift codebases. You compile the pattern once, run it against a string, and convert each NSTextCheckingResult range back into a Swift range before slicing the string.

swift
1import Foundation
2
3let text = "Order 123 ships on 2026-03-11 and invoice 456 closes on 2026-03-12."
4let pattern = #"\d+"#
5
6do {
7    let regex = try NSRegularExpression(pattern: pattern)
8    let searchRange = NSRange(text.startIndex..<text.endIndex, in: text)
9    let matches = regex.matches(in: text, range: searchRange)
10
11    let numbers = matches.compactMap { match -> String? in
12        guard let range = Range(match.range, in: text) else {
13            return nil
14        }
15        return String(text[range])
16    }
17
18    print(numbers)
19} catch {
20    print("Invalid pattern: \(error)")
21}

The key step is Range(match.range, in: text). You should not try to use NSRange offsets directly on Swift strings, because Swift strings are Unicode-correct and are not indexed by simple integer positions.

Extract capture groups, not just whole matches

Real regex work often needs submatches. For example, if you want both the count and the fruit name from a sentence, capture groups are the right tool.

swift
1import Foundation
2
3let text = "We packed 15 apples and 9 oranges."
4let pattern = #"(\d+)\s+(apples|oranges)"#
5
6do {
7    let regex = try NSRegularExpression(pattern: pattern)
8    let searchRange = NSRange(text.startIndex..<text.endIndex, in: text)
9
10    for match in regex.matches(in: text, range: searchRange) {
11        guard
12            let countRange = Range(match.range(at: 1), in: text),
13            let fruitRange = Range(match.range(at: 2), in: text)
14        else {
15            continue
16        }
17
18        let count = String(text[countRange])
19        let fruit = String(text[fruitRange])
20        print("\(count) \(fruit)")
21    }
22} catch {
23    print("Invalid pattern: \(error)")
24}

Group 0 is always the whole match. Capturing groups begin at index 1, which is why range(at: 1) and range(at: 2) extract the subparts.

Reuse compiled regex objects

If you run the same pattern repeatedly, compile it once and reuse it. That keeps the hot path cleaner and avoids repeating the pattern-validation cost.

swift
1import Foundation
2
3struct InvoiceParser {
4    private let regex = try! NSRegularExpression(pattern: #"INV-(\d{4})"#)
5
6    func extractInvoiceIds(from text: String) -> [String] {
7        let range = NSRange(text.startIndex..<text.endIndex, in: text)
8
9        return regex.matches(in: text, range: range).compactMap { match in
10            guard let valueRange = Range(match.range(at: 1), in: text) else {
11                return nil
12            }
13            return String(text[valueRange])
14        }
15    }
16}
17
18let parser = InvoiceParser()
19print(parser.extractInvoiceIds(from: "INV-1001, INV-1002"))

Using try! here is reasonable because the pattern is a hard-coded programmer constant. If the pattern is dynamic or user-supplied, keep the do and catch flow instead.

Newer Swift regex syntax exists, but compatibility matters

Newer Swift versions also have native regex features, which can feel more Swifty than NSRegularExpression. That is a good option in modern projects, but NSRegularExpression is still the safest baseline when you are writing code that must fit older app targets or mixed UIKit and Foundation-heavy code.

So if you are answering "how do I extract regex matches in Swift?" for the broadest audience, Foundation remains the most portable answer.

Common Pitfalls

The most common mistake is trying to slice a Swift string directly with NSRange integers. Always convert through Range(..., in: text) first.

Another issue is forgetting that capture group 0 is the full match. If you want the first parenthesized part, use range(at: 1).

Developers also recreate the regex for every call even when the pattern never changes. Compiling once is usually simpler and faster.

Finally, remember that regex patterns live inside Swift string literals, so escaping rules matter. Raw string syntax such as #"...pattern..."# often makes patterns much easier to read.

Summary

  • In Swift, NSRegularExpression is the most compatible way to extract regex matches.
  • Convert every NSRange back to a Swift Range before slicing the string.
  • Use capture groups with range(at:) when you need structured submatches.
  • Reuse compiled regex objects when the same pattern is used repeatedly.
  • Raw string literals make regex patterns easier to write and maintain.

Course illustration
Course illustration

All Rights Reserved.