If you’ve ever needed to check whether a user typed a valid email address, extract a phone number from a block of text, or find every URL in a string, you’ve run into a problem that Swift regex is designed to solve.
Regex stands for “regular expression.” It’s a way to describe a pattern in text using a special mini-language. Instead of writing out a long chain of string operations to hunt for something specific, you write a compact pattern and let Swift do the matching for you. Think of it like a search query on steroids. Where a normal search finds exact words, a regex finds anything that fits a shape you define.
Here’s the thing though: regex has a reputation for being intimidating. You look at something like /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i and your brain goes blank. That’s completely normal. In this guide, I’m going to take you from zero and build up the concepts one at a time. By the end, you’ll understand how to use regex in Swift, which approach to choose (there are three), and how it shows up in real iOS code like form validation.
The Basic Syntax
In Swift 5.7 and later, there are three ways to create a regex. We’ll cover all three later, but let’s start with the most common one: a regex literal. It looks like a forward slash sandwich.
Here’s the simplest possible regex example in Swift:
import Foundation
let input = "Order number: 4821"
let pattern = /\d+/
if let match = input.firstMatch(of: pattern) {
print("Found: \(match.0)") // Found: 4821
} else {
print("No match found")
}Let’s go line by line:
| Line | What it does |
|---|---|
let input = "Order number: 4821" | This is the string we want to search through. In a real app this might be user input or data from an API. |
let pattern = /\d+/ | This is a regex literal. The slashes are delimiters, like quotation marks for a String. Inside, \d means “any digit character” and + means “one or more of the thing before it.” So this pattern matches one or more digits. |
input.firstMatch(of: pattern) | firstMatch(of:) is a method on String that searches for the first part of the string matching your pattern. It returns an optional because the match might not exist. |
if let match = ... | Standard optional binding. If a match was found, we unwrap it and use it. If not, we fall through to the else. |
match.0 | The .0 gives you the portion of the string that was matched. It returns a Substring, which you can convert to a String if needed. |
Breaking It Down
Before we go further, let me explain the key building blocks. You don’t need to memorize all of these right now. Think of this as a vocabulary list you’ll refer back to as you use regex more.
Metacharacters: the special symbols
Regular characters in a regex just match themselves. The letter a matches the letter “a”. But certain characters have special meaning, and these are called metacharacters. Here are the ones you’ll use most:
\dmatches any single digit (0 through 9)\wmatches any word character (letters, digits, underscores)\smatches any whitespace character (space, tab, newline).matches any single character except a newline^matches the beginning of a string$matches the end of a string
Quantifiers: how many to match
After any character or metacharacter, you can add a quantifier to say how many times it should appear:
+means one or more*means zero or more?means zero or one (makes something optional){3}means exactly three times{2,4}means between two and four times
Character classes: match a set of options
Square brackets let you define a set of characters to match. [aeiou] matches any vowel. [A-Z] matches any uppercase letter. [0-9] matches any digit (same as \d).
Three ways to create a regex in Swift
Swift 5.7 (iOS 16+) introduced a proper native Regex type. Before that, developers used NSRegularExpression, which is verbose and requires working with NSRange instead of Swift ranges. Today you have three modern options:
- Regex literal:
/pattern/— compiled at build time, type-safe, Xcode highlights it - Regex from string:
try Regex("pattern")— created at runtime, useful for dynamic patterns - RegexBuilder DSL: A result builder API that reads like Swift code, easier to understand for complex patterns
Regex type, regex literals (/.../), and RegexBuilder all require iOS 16+ / Swift 5.7+. If you need to support older iOS versions, you will still need to use NSRegularExpression. For any new project targeting iOS 16 or later, use the modern approach covered in this article.Variations and Syntax Patterns
Now let’s look at the most important variations you’ll use in real code.
let pattern = /[A-Z][a-z]+/ // matches a capitalized word
let name = "Hello, Chris!"
if let match = name.firstMatch(of: pattern) {
print(match.0) // "Hello"
}let userPattern = "[0-9]+" // could come from an API or user input
do {
let regex = try Regex(userPattern)
if let match = "Total: 42".firstMatch(of: regex) {
print(match.0) // "42"
}
} catch {
print("Invalid pattern: \(error)")
}Regex<AnyRegexOutput> rather than a typed regex.import RegexBuilder
let pattern = Regex {
OneOrMore(.word) // one or more word characters
"@" // literal @ sign
OneOrMore(.word) // domain name part
"." // literal dot
OneOrMore(.word) // TLD like "com"
}
if let match = "Email: chris@cwc.com".firstMatch(of: pattern) {
print(match.0) // "chris@cwc.com"
}RegexBuilder to get access to this DSL. It’s more verbose but dramatically more readable for complex patterns. You use named components like OneOrMore, ZeroOrMore, Optionally, and ChoiceOf instead of cryptic symbols. Great for team codebases where others need to understand your regex at a glance.// Named captures using regex literal
let pattern = /(?<first>\w+)\s+(?<last>\w+)/
let fullName = "Chris Ching"
if let match = fullName.firstMatch(of: pattern) {
print(match.first) // "Chris"
print(match.last) // "Ching"
}(?<name>pattern). Swift then lets you access the captured value using match.name directly. The whole match is always at match.0.let zipCode = "M5V 3A8"
// firstMatch finds the pattern anywhere in the string
let hasDigits = zipCode.firstMatch(of: /\d/) != nil // true
// wholeMatch requires the pattern to match the ENTIRE string
let isValidZip = zipCode.wholeMatch(of: /[A-Z]\d[A-Z]\s\d[A-Z]\d/) != nil // truefirstMatch(of:) finds the first occurrence of the pattern anywhere inside the string. wholeMatch(of:) requires the pattern to match the entire input string, with nothing left over. Use wholeMatch for validation (like checking if a field is a valid email). Use firstMatch for searching or extraction.let messy = "Phone: 416-555-1234 ext. 21"
// Replace multiple spaces with a single space
let clean = messy.replacing(/\s+/, with: " ")
// "Phone: 416-555-1234 ext. 21"
// Split a string on any non-digit
let digits = "416-555-1234".split(separator: /\D/)
// ["416", "555", "1234"]replacing(_:with:) and split(separator:) in iOS 16. These are great for cleaning up data before processing it. \D (uppercase) is the inverse of \d and matches any non-digit character.How Swift Regex Appears in Real iOS Code
The most practical use of regex in iOS development is form validation. If you’ve ever built a registration screen, a contact form, or a settings page, you’ve needed to check things like “is this a valid email?” or “is this phone number formatted correctly?”
Here’s a realistic example: a SwiftUI form view with regex-powered validation built using RegexBuilder for readability.
import SwiftUI
import RegexBuilder
struct SignUpFormView: View {
@State private var email = ""
@State private var phone = ""
// A simple email validator using regex literal
var isEmailValid: Bool {
let pattern = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/
return email.wholeMatch(of: pattern) != nil
}
// A North American phone validator using RegexBuilder
var isPhoneValid: Bool {
let pattern = Regex {
Optionally { "1-" } // optional country code
Repeat(count: 3) { One(.digit) } // area code: 3 digits
"-" // dash separator
Repeat(count: 3) { One(.digit) } // prefix: 3 digits
"-" // dash separator
Repeat(count: 4) { One(.digit) } // line: 4 digits
}
return phone.wholeMatch(of: pattern) != nil
}
var body: some View {
Form {
Section("Contact Info") {
TextField("Email address", text: $email)
.keyboardType(.emailAddress)
.autocapitalization(.none)
.foregroundStyle(isEmailValid ? .primary : .red)
TextField("Phone (416-555-1234)", text: $phone)
.keyboardType(.phonePad)
.foregroundStyle(isPhoneValid ? .primary : .red)
}
Button("Create Account") {
// submit action
}
.disabled(!isEmailValid || !isPhoneValid)
}
}
}Here’s what each piece is doing:
The email validation uses a regex literal. The pattern checks for characters before an @, characters in the domain, a literal dot, then at least two letters for the top-level domain. wholeMatch ensures the entire field matches, not just part of it. So “hello@” would fail, but “hello@cwc.com” would pass.
The phone validation uses RegexBuilder. This makes the pattern easier to read and maintain. Optionally { "1-" } means the user can optionally include a country code. Repeat(count: 3) { One(.digit) } matches exactly three digits. The pattern expects the format 416-555-1234.
The computed properties (isEmailValid and isPhoneValid) return a Bool. SwiftUI re-evaluates these automatically whenever the state changes, so validation is live as the user types.
The button is disabled until both fields are valid. This is a pattern you’ll use constantly in real apps.
Common Mistakes to Avoid
These are the mistakes I see come up most often in the CWC community when people first start using regex in Swift.
1. Using firstMatch when you need wholeMatch for validation
This one trips up almost everyone. Imagine you’re validating an email field and you write:
let email = "notanemail hello@cwc.com garbage"
let isValid = email.firstMatch(of: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/) != nil
// isValid is TRUE, even though the full string is not a valid emailfirstMatch found a valid email address within the string and returned true, even though the overall string is clearly not just an email. For validation, always use wholeMatch. It requires the entire string to match the pattern.
wholeMatch. If you’re searching “does this text contain an X?”, use firstMatch.2. Forgetting to escape backslashes in string-based regex
In regex literals (/.../), you write \d directly. But when creating a Regex from a string, the backslash needs to be escaped.
// Regex literal — works fine
let p1 = /\d+/
// String-based — backslash must be escaped with another backslash
let p2 = try Regex("\\d+") // correct
let p3 = try Regex("\d+") // wrong — Swift sees \d as an escape sequenceYou can also use raw string literals with #"..."# to avoid the double backslash: try Regex(#"\d+"#). This matches how the regex literal behaves and is easier to read.
3. Not handling the throwing initializer for string-based Regex
When you create a Regex from a string, it throws if the pattern is invalid. A common mistake is using try! and forgetting about it:
// Risky — crashes if the pattern is ever invalid
let regex = try! Regex(someUserProvidedPattern)
// Safe — handles the error gracefully
do {
let regex = try Regex(someUserProvidedPattern)
// use the regex
} catch {
print("Invalid pattern: \(error.localizedDescription)")
}If the pattern is a hardcoded literal you wrote yourself and you know it’s valid, try! is acceptable. If the pattern comes from outside your code (an API, user input, a config file), always use a do-catch.
4. Expecting regex to handle everything
Regex is a powerful tool, but it’s not always the right one. A common beginner mistake is trying to write a single regex to fully validate complex formats like email addresses or URLs. Email validation in particular is surprisingly complex. The pattern I showed earlier in this article catches the most common cases but isn’t RFC 5322 compliant.
For most forms, a simple regex plus a meaningful error message works better than a “perfect” regex that nobody on your team can understand. Pair regex with other Swift validation logic when needed rather than trying to encode everything into one pattern.
Quick Reference
| Syntax | What It Does |
|---|---|
| /pattern/ | Regex literal (compile-time checked, iOS 16+) |
| try Regex(“pattern”) | Create regex from string at runtime |
| \d | Any digit (0–9) |
| \w | Any word character (letter, digit, underscore) |
| \s | Any whitespace (space, tab, newline) |
| . | Any single character except newline |
| + | One or more of the preceding item |
| * | Zero or more of the preceding item |
| ? | Zero or one (makes the preceding item optional) |
| {n} | Exactly n times |
| [A-Z] | Any character in the specified range |
| ^ | Start of string (or line in multiline mode) |
| $ | End of string (or line in multiline mode) |
| (?<name>pattern) | Named capture group |
| .firstMatch(of:) | Find first occurrence of pattern anywhere in string |
| .wholeMatch(of:) | Require entire string to match the pattern |
| .prefixMatch(of:) | Require string to start with the pattern |
| .replacing(_:with:) | Replace matches with a different string |
| .split(separator:) | Split string on regex pattern matches |
| OneOrMore(.digit) | RegexBuilder: one or more digits |
| Optionally { } | RegexBuilder: optional component |
| Capture { } | RegexBuilder: capture a matched group |
| ChoiceOf { } | RegexBuilder: match one of several options |
Using AI to Go Deeper with Swift Regex
AI tools like Claude, Copilot, and Cursor have become a normal part of how developers work. With regex especially, knowing how to use AI as a tutor can save you hours of frustration. The key is using AI to build understanding, not just to get a pattern you paste without knowing what it does.
Summary
Here’s a quick recap of what you learned about Swift regex:
- A regex is a pattern written in a special mini-language that describes what text you’re looking for
- Swift 5.7 (iOS 16+) introduced a native
Regextype with three creation methods: literals (/.../), strings (try Regex("...")), and RegexBuilder DSL - Regex literals are the most common choice and offer compile-time checking and Xcode syntax highlighting
- Use
wholeMatchfor validation,firstMatchfor searching, andprefixMatchfor prefix checking - Named captures let you extract specific parts of a match with clean property access like
match.name - RegexBuilder is more verbose but dramatically more readable for complex patterns used in production code
- The most common mistake is using
firstMatchwhen you needwholeMatchfor validation
The best way to get comfortable with regex is to use it on a real field in a real form. Pick something simple like a Canadian postal code or a 5-digit US zip code and build a validator. Once you’ve done that once, the rest comes naturally.
A great next topic after regex is Swift string manipulation more broadly, including contains, hasPrefix, components(separatedBy:), and trimmingCharacters(in:). Regex and these built-in methods complement each other well in real iOS apps.

