avatar
Published on

How to Replace Characters in a String in Swift

Authors
  • avatar
    Name
    Mick MacCallum
    Twitter
    @0x7fs

Replacing characters or substrings is a common string manipulation task in Swift. Whether you're formatting URLs, sanitizing user input, or transforming text data, Swift provides several approaches to accomplish this. This article covers the most effective methods for string replacement, from simple character substitution to complex pattern matching.

Using replacingOccurrences(of:with:)

The most straightforward way to replace characters or substrings is using the replacingOccurrences(of:with:) method. This method searches for all occurrences of a specified string and replaces them with another string.

Basic Usage

let aString = "This is my string"
let newString = aString.replacingOccurrences(of: " ", with: "+")
print(newString) // "This+is+my+string"

The method has two optional parameters that give you more control over the replacement:

let aString = "This is my string"
let newString = aString.replacingOccurrences(
    of: " ",
    with: "+",
    options: .literal,
    range: nil
)

String Comparison Options

The options parameter accepts a String.CompareOptions value that modifies how the search is performed:

let text = "Hello World, hello Swift"

// Case-insensitive replacement
let result1 = text.replacingOccurrences(
    of: "hello",
    with: "Hi",
    options: .caseInsensitive
)
print(result1) // "Hi World, Hi Swift"

// Literal comparison (default)
let result2 = text.replacingOccurrences(
    of: "hello",
    with: "Hi",
    options: .literal
)
print(result2) // "Hello World, Hi Swift"

Targeting a Specific Range

You can limit the replacement to a specific range within the string:

let text = "The cat sat on the mat"
let range = text.range(of: "cat")!.upperBound..<text.endIndex

let result = text.replacingOccurrences(
    of: "at",
    with: "OP",
    range: range
)
print(result) // "The cat sOP on the mOP"

Using components(separatedBy:) and joined(separator:)

When you're replacing a separator character with another separator, you can split the string into components and rejoin them with a new separator. This approach is particularly clean for formatting operations:

let aString = "This is my string"
let components = aString.components(separatedBy: " ")
let newString = components.joined(separator: "+")
print(newString) // "This+is+my+string"

This method is especially useful when you need to perform additional operations on the components:

let csvData = "apple,banana,cherry,date"
let fruits = csvData.components(separatedBy: ",")
    .map { $0.capitalized }
let formatted = fruits.joined(separator: " | ")
print(formatted) // "Apple | Banana | Cherry | Date"

Using map() for Character-Level Replacement

For a more functional programming approach that doesn't rely on Foundation's NSString APIs, you can use map() to transform individual characters:

let aString = "Some search text"

let replaced = String(aString.map {
    $0 == " " ? "+" : $0
})
print(replaced) // "Some+search+text"

This approach gives you fine-grained control over the transformation logic:

let text = "Hello World 123"

// Replace digits with asterisks
let censored = String(text.map {
    $0.isNumber ? "*" : $0
})
print(censored) // "Hello World ***"

// Replace vowels with underscores
let noVowels = String(text.map {
    "aeiouAEIOU".contains($0) ? "_" : $0
})
print(noVowels) // "H_ll_ W_rld 123"

Using Regular Expressions

For complex pattern matching and replacement, regular expressions provide powerful capabilities. Swift 5.7 introduced the new Regex API, making pattern matching more type-safe and easier to use.

Using NSRegularExpression (Traditional Approach)

import Foundation

let text = "My phone number is 555-1234 and my zip is 12345"
let pattern = "\\d{3}-\\d{4}"

if let regex = try? NSRegularExpression(pattern: pattern) {
    let result = regex.stringByReplacingMatches(
        in: text,
        range: NSRange(text.startIndex..., in: text),
        withTemplate: "***-****"
    )
    print(result) // "My phone number is ***-**** and my zip is 12345"
}

Using Swift Regex (Swift 5.7+)

let text = "Contact me at user@example.com or admin@test.org"
let emailPattern = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/

let censored = text.replacing(emailPattern, with: "[REDACTED]")
print(censored) // "Contact me at [REDACTED] or [REDACTED]"

The new Regex API also supports more complex replacements with capture groups:

let text = "John Smith (age: 30), Jane Doe (age: 25)"
let pattern = /(\w+)\s+(\w+)\s+\(age:\s+\d+\)/

let result = text.replacing(pattern) { match in
    "\(match.output.2), \(match.output.1)" // Reverse first and last name
}
print(result) // "Smith, John, Doe, Jane"

Using RangeReplaceableCollection Methods

For more control over the replacement process, you can use lower-level methods like replaceSubrange(_:with:):

var text = "Hello World"
if let range = text.range(of: "World") {
    text.replaceSubrange(range, with: "Swift")
}
print(text) // "Hello Swift"

This approach is useful when you need to replace content while building up a string:

var markdown = "Check out [this link](url) and [another](url2)"
var mutableMarkdown = markdown

while let range = mutableMarkdown.range(of: "[", options: .literal) {
    if let endRange = mutableMarkdown.range(of: "]", options: .literal, range: range.upperBound..<mutableMarkdown.endIndex) {
        mutableMarkdown.replaceSubrange(range.lowerBound..<endRange.upperBound, with: "")
    } else {
        break
    }
}
print(mutableMarkdown) // "Check out (url) and (url2)"

Replacing Multiple Different Characters

Sometimes you need to replace multiple different characters in a single pass. Here are several approaches:

Using reduce()

let text = "Hello, World! How are you?"
let unwantedChars = [",", "!", "?"]

let cleaned = unwantedChars.reduce(text) { result, char in
    result.replacingOccurrences(of: char, with: "")
}
print(cleaned) // "Hello World How are you"

Using Character Sets

import Foundation

let text = "Hello123World456"
let digits = CharacterSet.decimalDigits

let result = String(text.unicodeScalars.map {
    digits.contains($0) ? Character("*") : Character($0)
})
print(result) // "Hello***World***"

Creating a Replacement Dictionary

let text = "Hello World"
let replacements: [Character: Character] = [
    "H": "h",
    "W": "w",
    "o": "0",
    "l": "1"
]

let result = String(text.map { replacements[$0] ?? $0 })
print(result) // "he110 w0r1d"

Performance Considerations

Different replacement methods have different performance characteristics:

  1. replacingOccurrences(of:with:) - Best for simple, one-time replacements of known substrings
  2. components/joined - Efficient when you're replacing a single separator character
  3. map() - Good for character-level transformations with custom logic
  4. Regular expressions - Best for complex pattern matching, but with overhead for compilation
  5. replaceSubrange - Most efficient for targeted replacements when you already know the range

For multiple replacements on the same string, consider which approach minimizes the number of passes through the string.

Common Use Cases

URL Encoding Spaces

let searchQuery = "swift string methods"
let encoded = searchQuery.replacingOccurrences(of: " ", with: "+")
// Or use proper URL encoding
let properlyEncoded = searchQuery.addingPercentEncoding(
    withAllowedCharacters: .urlQueryAllowed
)

Sanitizing User Input

func sanitize(_ input: String) -> String {
    let dangerous = ["<", ">", "&", "\"", "'"]
    let safe = ["&lt;", "&gt;", "&amp;", "&quot;", "&#x27;"]

    var result = input
    for (index, char) in dangerous.enumerated() {
        result = result.replacingOccurrences(of: char, with: safe[index])
    }
    return result
}

let userInput = "<script>alert('XSS')</script>"
print(sanitize(userInput))
// &lt;script&gt;alert(&#x27;XSS&#x27;)&lt;/script&gt;

Formatting Phone Numbers

let phoneDigits = "5551234567"
let components = phoneDigits.components(separatedBy: CharacterSet.decimalDigits.inverted)
let digitsOnly = components.joined()

if digitsOnly.count == 10 {
    let areaCode = digitsOnly.prefix(3)
    let prefix = digitsOnly.dropFirst(3).prefix(3)
    let suffix = digitsOnly.suffix(4)
    let formatted = "(\(areaCode)) \(prefix)-\(suffix)"
    print(formatted) // "(555) 123-4567"
}

Conclusion

Swift provides multiple ways to replace characters and substrings, each suited for different scenarios:

  • Use replacingOccurrences(of:with:) for straightforward substring replacement
  • Use components/joined when replacing separators
  • Use map() for functional character transformations
  • Use regular expressions for complex pattern matching
  • Use replaceSubrange for precise, range-based replacements

Choose the method that best fits your use case, considering both code clarity and performance requirements.