Swift String Replacement: Replace Characters the Right Way

Swift String Replacement: Replace Characters the Right Way

Written by

in

Quick answer: Use replacingOccurrences(of:with:) from Foundation for any deployment target, or replacing(_:with:) on iOS 16+ / Swift 5.7. Both return a new String — Swift strings are value types, so you must assign the result back. For a single match, pass maxReplacements: 1 or use replacingCharacters(in:with:) with a range.

Why this happens

Swift’s String has no replace(_:with:) method. If you write myString.replace(" ", with: "+") the compiler stops with value of type 'String' has no member 'replace'. Most people hit this after copying a snippet from an old tutorial, because the method was renamed in Swift 3 and the pre-Swift 3 name no longer exists either.

The second half of the confusion is value semantics. String is a struct, so no replacement method mutates the receiver in place. Every API below returns a brand-new string, and the original is untouched until you assign the result back to a variable.

Finally, there are now two families of APIs: the long-standing Foundation method replacingOccurrences(of:with:options:range:), and the newer standard-library replacing(_:with:maxReplacements:) added in Swift 5.7 (iOS 16, macOS 13). Both are correct; the choice depends on your deployment target and whether you want the extra options.

How to fix it

1. Use replacingOccurrences(of:with:) for any deployment target

This is the Foundation method that has been the standard answer since Swift 3. Import Foundation and call it on the string:

import Foundation

let text = "This is my string"
let slug = text.replacingOccurrences(of: " ", with: "+")
// "This+is+my+string"

The options and range parameters are optional. When you need them, they give you case-insensitive matching, regular expressions, or a limited search window:

let shout = "Hello World".replacingOccurrences(
    of: "world",
    with: "Swift",
    options: .caseInsensitive
)
// "Hello Swift"

let digitsGone = "a1b2c3".replacingOccurrences(
    of: "[0-9]",
    with: "",
    options: .regularExpression
)
// "abc"

If you only want to touch part of the string, pass a range instead of nil:

let full = "one two one"
let head = full.startIndex..<full.index(full.startIndex, offsetBy: 7)
let partial = full.replacingOccurrences(of: "one", with: "1", range: head)
// "1 two one"

2. Use replacing(_:with:maxReplacements:) on iOS 16 and later

Swift 5.7 added a native replacement method to RangeReplaceableCollection, which String conforms to. It needs no Foundation import and reads more cleanly:

let text = "This is my string with spaces"
let slug = text.replacing(" ", with: "+")
// "This+is+my+string+with+spaces"

The maxReplacements argument caps how many matches are swapped. It defaults to Int.max, so omitting it replaces everything:

let limited = text.replacing(" ", with: "+", maxReplacements: 2)
// "This+is+my string with spaces"

There is also a closure overload, useful when the replacement depends on what was matched:

let masked = "a1b2".replacing(/[0-9]/) { _ in "#" }
// "a#b#"

Remember the availability floor: this API is iOS 16.0+, macOS 13.0+, tvOS 16.0+, watchOS 9.0+. If your deployment target is lower, stay with replacingOccurrences.

3. Replace exactly one occurrence

Neither method above stops at the first match unless you tell it to. On iOS 16+ pass maxReplacements: 1. On older targets, find the range and splice it:

import Foundation

var text = "one two one"
if let range = text.range(of: "one") {
    text = text.replacingCharacters(in: range, with: "1")
}
// "1 two one"

replacingCharacters(in:with:) is the right tool whenever you already have a Range<String.Index> — for example from a search field highlight — because it avoids re-scanning the whole string.

4. Swap many characters in one pass

Chaining replacingOccurrences calls re-scans the string each time. For a handful of characters, a single map over the string is faster and easier to read:

let table: [Character: Character] = [" ": "+", "-": "_", ".": "-"]
let input = "my file-name.txt"
let output = String(input.map { table[$0] ?? $0 })
// "my+file_name-txt"

For the specific case of turning a phrase into a URL slug, components(separatedBy:) plus joined(separator:) is a common alternative, though it only handles one separator at a time:

let slug = "This is my string"
    .components(separatedBy: " ")
    .joined(separator: "+")

If you genuinely need reference semantics — a shared buffer mutated by several objects — wrap the text in NSMutableString and call replaceOccurrences(of:with:options:range:) on it. In SwiftUI and most app code you should not need this.

How to prevent it

  • Don’t paste Swift 2 snippets: stringByReplacingOccurrencesOfString(_:withString:options:range:) was renamed to replacingOccurrences(of:with:options:range:) in Swift 3 and the old name is gone.
  • Always assign the result. text.replacingOccurrences(...) on its own line compiles and does nothing, because String is a value type.
  • Check your deployment target before reaching for replacing(_:with:); it is iOS 16+ only.
  • Prefer .literal matching when the search string contains regex metacharacters like ., * or [, otherwise .regularExpression will surprise you.
  • Be careful with combining characters and emoji. replacingOccurrences works on UTF-16 units, so replacing a base letter inside a decomposed sequence can split a grapheme cluster.
  • Write a unit test with an empty string and with a string that contains no matches — both should return the input unchanged.

FAQ

Why does myString.replace(" ", with: "+") not compile?

Because String has no method named replace. The compiler reports value of type 'String' has no member 'replace'. Use replacingOccurrences(of:with:) or, on iOS 16+, replacing(_:with:).

Is replacingOccurrences(of:with:) case-sensitive?

Yes, by default it performs a literal, case-sensitive comparison. Pass options: .caseInsensitive to match regardless of case, or combine options such as [.caseInsensitive, .diacriticInsensitive].

Can I replace text with a regular expression in Swift?

Yes. Pass options: .regularExpression to replacingOccurrences(of:with:) and write the pattern as the of argument, for example "\\s+" to collapse runs of whitespace into a single character.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.