Swift Regular Expressions: Regex Literals and RegexBuilder
Swift includes modern, type-safe support for regular expressions, so you can search text, validate input, extract values, and transform strings without relying only on older Foundation APIs. In this article, you will learn what Swift regular expressions are, how regex literals and RegexBuilder work, when to use each approach, and how to avoid the mistakes that commonly confuse beginners.
Quick answer: In Swift 5+, you can write regular expressions either as regex literals such as /\d+/ or by building them with RegexBuilder. Use literals when the pattern is short and familiar, and use RegexBuilder when you want clearer, more structured, and more type-safe patterns.
Difficulty: Intermediate
Helpful to know first: You will understand this better if you already know basic Swift syntax, how String values work, and how functions and conditionals are used in simple programs.
1. What Is Swift Regular Expressions?
A regular expression, usually called a regex, is a pattern used to find, validate, or extract text. Swift’s modern regex system lets you describe text patterns directly in Swift code and work with matches in a safer way than many older regex APIs.
- A regex describes a text pattern rather than a fixed string.
- You can use regex to check whether text matches a format, such as an email-like value or an order number.
- You can extract parts of text, such as IDs, dates, or filenames.
- Swift supports both regex literals and the builder-style RegexBuilder API.
- Swift regex is often easier to integrate with the language than older NSRegularExpression-based code.
For example, a simple regex can find one or more digits inside a string. More advanced patterns can capture multiple parts, such as a username and domain from an email-like value.
Swift regex support is powerful, but it still helps to think carefully about what text you want to match. A regex that is too broad can accept invalid input, while one that is too strict can reject valid data.
2. Why Swift Regular Expressions Matters
Text processing appears in many real apps. User input, log files, imported CSV data, API responses, filenames, and command-line arguments often contain structured text that needs to be checked or parsed.
You would use Swift regular expressions when:
- You need to validate a format such as a postal code, tag, code, or identifier.
- You want to find values embedded in larger text.
- You need to capture specific pieces from a string, such as year, month, and day.
- You want more flexible matching than contains, hasPrefix, or hasSuffix can provide.
You would not automatically use regex for every string task. If a simple string method expresses the intent more clearly, that is often the better choice. For example, checking whether a file ends with .jpg is usually clearer with hasSuffix than with a regex.
3. Basic Syntax or Core Idea
Swift gives you two main ways to create a regex: a regex literal and RegexBuilder.
Using a regex literal
A regex literal is written between slashes. This example looks for one or more digits.
let text = "Order 4829 shipped"
let regex = /\d+/
if let match = try? text.firstMatch(of: regex) {
print(match.0)
}Here is what each part means:
- /\d+/ means one or more digits.
- firstMatch(of:) searches the string for the first matching part.
- match.0 refers to the full text that matched.
This prints the first number found in the string.
Using RegexBuilder
RegexBuilder lets you describe the same pattern using readable Swift components.
import RegexBuilder
let text = "Order 4829 shipped"
let regex = Regex {
OneOrMore {
CharacterClass.digit
}
}
if let match = try? text.firstMatch(of: regex) {
print(match.0)
}This version matches the same digits, but it is often easier to read when patterns become longer or more structured.
Whole match vs first match
A common point of confusion is the difference between matching part of a string and matching the entire string.
let code = "AB-123"
let regex = /[A-Z]{2}-\d{3}/
let partial = try? code.firstMatch(of: regex)
let whole = try? code.wholeMatch(of: regex)firstMatch(of:) looks for a matching part somewhere in the string. wholeMatch(of:) requires the entire string to fit the pattern.
4. Step-by-Step Examples
Example 1: Find the first number in a sentence
This is one of the simplest practical uses of regex. The pattern below finds a sequence of digits.
let message = "Your table number is 27."
let numberRegex = /\d+/
if let match = try? message.firstMatch(of: numberRegex) {
print("Found number:", match.0)
}This prints the first digit sequence found in the sentence. It is useful for basic extraction tasks from mixed text.
Example 2: Validate a simple product code
When you want the entire input to match a pattern, use wholeMatch(of:). This example expects exactly two uppercase letters, a dash, and three digits.
let productCode = "TV-204"
let productRegex = /[A-Z]{2}-\d{3}/
if (try? productCode.wholeMatch(of: productRegex)) != nil {
print("Valid product code")
} else {
print("Invalid product code")
}This is better than a partial search because the whole input must match the format, not just one part of it.
Example 3: Capture parts of a date
Regex becomes especially useful when you want to extract multiple values from one string.
let dateText = "2025-08-17"
let dateRegex = /(\d{4})-(\d{2})-(\d{2})/
if let match = try? dateText.wholeMatch(of: dateRegex) {
let year = match.1
let month = match.2
let day = match.3
print("Year:", year)
print("Month:", month)
print("Day:", day)
}The full match is stored at match.0, and captured groups begin at match.1. This is a common way to pull structured values from formatted text.
Example 4: Build the same date pattern with RegexBuilder
For complex patterns, RegexBuilder can be easier to read and maintain.
import RegexBuilder
let dateText = "2025-08-17"
let dateRegex = Regex {
Capture {
Repeat(4) {
CharacterClass.digit
}
}
"-"
Capture {
Repeat(2) {
CharacterClass.digit
}
}
"-"
Capture {
Repeat(2) {
CharacterClass.digit
}
}
}
if let match = try? dateText.wholeMatch(of: dateRegex) {
print(match.1, match.2, match.3)
}This example shows the same idea as the literal version, but with a builder structure that many developers find easier to edit later.
5. Practical Use Cases
- Checking whether a user-entered code matches a required format before submitting a form.
- Extracting order numbers, invoice IDs, or ticket references from messages.
- Parsing simple text-based data formats when full JSON or XML parsing is unnecessary.
- Finding hashtags, mentions, or tagged values inside text content.
- Cleaning imported data by detecting extra spaces, separators, or formatting mistakes.
- Scanning logs for timestamps, error codes, or repeated patterns.
6. Common Mistakes
Mistake 1: Using firstMatch when the entire string must be valid
Beginners often search for a match anywhere in the string when they actually want to validate the whole input.
Problem: This code accepts invalid input because it only checks whether some part of the string matches the pattern.
let input = "Code: AB-123 extra"
let regex = /[A-Z]{2}-\d{3}/
if (try? input.firstMatch(of: regex)) != nil {
print("Accepted")
}Fix: Use wholeMatch(of:) when the entire input must follow the expected format.
let input = "AB-123"
let regex = /[A-Z]{2}-\d{3}/
if (try? input.wholeMatch(of: regex)) != nil {
print("Accepted")
}The corrected version works because the whole string must match the regex, not just one substring.
Mistake 2: Forgetting that regex is not always the clearest tool
Some tasks are so simple that a regular expression makes the code harder to read than necessary.
Problem: This code uses a regex for a basic suffix check, which adds complexity without any real benefit.
let filename = "photo.jpg"
let regex = /.*\.jpg/
if (try? filename.wholeMatch(of: regex)) != nil {
print("JPEG file")
}Fix: Use direct string APIs when they express the intent more clearly.
let filename = "photo.jpg"
if filename.hasSuffix(".jpg") {
print("JPEG file")
}The corrected version works because hasSuffix is simpler, clearer, and designed for this exact check.
Mistake 3: Assuming a regex match always exists
Match methods return an optional result because the pattern may not be found.
Problem: This code force-unwraps a match that may be nil, which can crash at runtime if the text does not match.
let text = "No number here"
let regex = /\d+/
let match = try! text.firstMatch(of: regex)!
print(match.0)Fix: Safely unwrap the optional result before using the match.
let text = "No number here"
let regex = /\d+/
if let match = try? text.firstMatch(of: regex) {
print(match.0)
} else {
print("No match found")
}The corrected version works because it handles the case where the regex does not match the text.
Mistake 4: Choosing an unreadable literal when RegexBuilder would be clearer
Regex literals are concise, but complex patterns can become difficult to understand and maintain.
Problem: This pattern is compact, but it is harder to review and update because the structure is hidden inside punctuation-heavy syntax.
let regex = /(\d{4})-(\d{2})-(\d{2})/Fix: Use RegexBuilder when readability and maintenance matter more than compact syntax.
import RegexBuilder
let regex = Regex {
Capture {
Repeat(4) { CharacterClass.digit }
}
"-"
Capture {
Repeat(2) { CharacterClass.digit }
}
"-"
Capture {
Repeat(2) { CharacterClass.digit }
}
}The corrected version works because the pattern structure is easier to see and modify.
7. Best Practices
Practice 1: Start with the simplest string tool that solves the problem
Regex is powerful, but clarity matters. Prefer plain string APIs for straightforward tasks, and switch to regex when the pattern truly needs it.
// Less preferred for a simple prefix check
let regex = /ERROR:.*/
// Preferred
let line = "ERROR: Disk full"
if line.hasPrefix("ERROR:") {
print("Error line")
}This keeps simple code readable and reserves regex for cases where pattern matching is genuinely useful.
Practice 2: Use wholeMatch for validation rules
When checking a required format, make your intent explicit by requiring the entire string to match.
let zipCode = "12345"
let zipRegex = /\d{5}/
if (try? zipCode.wholeMatch(of: zipRegex)) != nil {
print("Looks valid")
}This practice reduces accidental false positives from partial matches.
Practice 3: Prefer RegexBuilder for long or business-critical patterns
If a regex is likely to be edited later, builder syntax can make maintenance safer and easier.
import RegexBuilder
let idRegex = Regex {
"ID-"
Repeat(6) {
CharacterClass.digit
}
}This version is easier to inspect than a compact literal when requirements change later.
8. Limitations and Edge Cases
- Regex can describe text patterns, but it does not automatically guarantee semantic validity. For example, a pattern can match 2025-99-99 even though that is not a real date.
- Some patterns become hard to read quickly. If teammates cannot understand the regex, maintenance becomes harder.
- firstMatch(of:) may succeed even when the full input is not valid. This is a common source of bugs in validation code.
- Captured values are based on the groups you define. If you change the pattern structure, capture positions such as match.1 and match.2 may change too.
- If you need older platform compatibility or existing Objective-C-based code, you may still encounter NSRegularExpression. Swift regex is often nicer to use, but older codebases may not be fully migrated.
- Very large or very complex patterns can be slower or more difficult to debug than a simpler parsing approach.
9. Swift Regex Literals vs RegexBuilder
These two approaches solve the same broad problem, but they are best in slightly different situations.
| Approach | Best for | Strength | Tradeoff |
|---|---|---|---|
| Regex literal | Short, familiar patterns | Compact and quick to write | Can become hard to read |
| RegexBuilder | Longer or structured patterns | Readable, composable, type-safe style | More verbose |
Use a regex literal when the pattern is short and the meaning is obvious, such as finding digits or checking a small code format. Use RegexBuilder when the pattern contains several parts, captures, or repeated rules that would be difficult to review in slash syntax.
Compared with NSRegularExpression, Swift’s modern regex support usually feels more natural in Swift code. The newer API integrates better with matches and captures, while older Foundation-based regex often requires more manual range handling.
10. Practical Mini Project
Let’s build a small validator and parser for order references. The required format is two uppercase letters, a dash, and four digits, such as AB-4829.
import RegexBuilder
let orderRegex = Regex {
Capture {
Repeat(2) {
CharacterClass("A"..."Z")
}
}
"-"
Capture {
Repeat(4) {
CharacterClass.digit
}
}
}
func parseOrderCode(_ text: String) {
if let match = try? text.wholeMatch(of: orderRegex) {
let prefix = match.1
let number = match.2
print("Valid order code")
print("Prefix:", prefix)
print("Number:", number)
} else {
print("Invalid order code:", text)
}
}
parseOrderCode("AB-4829")
parseOrderCode("A1-4829")
parseOrderCode("AB-48")This mini project validates the full input and extracts the two meaningful parts. It shows a realistic pattern: check a format, then use captured values in program logic.
11. Key Points
- Swift regular expressions are used to match, validate, and extract text patterns.
- Regex literals are concise and useful for short, familiar patterns.
- RegexBuilder is often better for long, structured, or frequently edited patterns.
- Use firstMatch(of:) to find a matching part and wholeMatch(of:) to validate the entire string.
- Captured groups let you extract specific pieces of a match, such as year, month, and day.
- Regex is powerful, but simple string methods are often clearer for simple tasks.
12. Practice Exercise
Build a program that checks whether a username follows this rule:
- It must start with a lowercase letter.
- It may contain lowercase letters and digits after that.
- Its total length must be between 4 and 12 characters.
Expected output: For a valid username, print Valid username. Otherwise, print Invalid username.
Hint: Use wholeMatch(of:) so the entire input must follow the rule.
let username = "alex42"
let usernameRegex = /[a-z][a-z0-9]{3,11}/
if (try? username.wholeMatch(of: usernameRegex)) != nil {
print("Valid username")
} else {
print("Invalid username")
}13. Final Summary
Swift regular expressions give you a modern way to work with text patterns directly in Swift. You can use regex literals for concise matching and RegexBuilder when you want a more readable and maintainable structure. Together, these tools make it easier to validate input, search strings, and extract useful data from text.
The most important habit is choosing the right level of complexity. Use regular expressions when the problem truly involves a pattern, but prefer simple string APIs when they are clearer. As a next step, practice writing small validation rules and capture-based parsers so you become comfortable deciding between regex literals, RegexBuilder, and ordinary string methods.