- July 26, 2025
- Mins Read
Swift’s String
type is Unicode-correct, performant, and safe—yet it can surprise newcomers because of grapheme clusters, copy-on-write behavior, and value semantics. In this guide you’ll master everything from basic literals to inserting/removing substrings and comparing values—so you can write clean, efficient, and bug-free iOS code.
Swift’s String
is a collection of Character
values, where each Character
represents an extended grapheme cluster (a human-readable symbol that might be composed of many Unicode scalars). This design keeps your UI text and localized content correct by default. Need the low-level scalars? unicodeScalars
has you covered, but most app code should stick to String
and Character
.
Further reading: the official Strings and Characters section in the Swift Language Guide on docs.swift.org.
A string literal is a fixed sequence of characters enclosed in double quotes:
let greeting = "Hello, Swift!"
For multi-line text, use triple quotes. Leading whitespace is preserved unless trimmed with a backslash:
let multiline = """
First line
Second line
"""
You can also control indentation by placing the closing """
on a new line.
Escape sequences let you embed characters that are hard to type or read:
\n
newline\t
tab\"
double quote\\
backslash\u{1F680}
Unicode scalar (🚀)let rocket = "Launch\nReady: \u{1F680}"
print(rocket)
// Launch
// Ready: 🚀
Raw string literals (#"..."#
) reduce escaping noise. Prefix and suffix the literal with #
to treat backslashes and quotes literally unless matched by the same number of #
signs:
let raw = #"Path: C:\\Users\\me"#
Create an empty string with a literal or initializer—both are equivalent:
var empty1 = ""
var empty2 = String()
Use isEmpty
to check for emptiness rather than comparing with ""
.
Strings are value types. Declare with var
to mutate, let
to keep immutable:
var name = "Mehul"
name += " Valand" // allowed
let fixed = "Swift"
// fixed += " 6" // compile-time error
Behind the scenes, Swift uses copy-on-write: copying is deferred until one copy mutates, keeping performance high.
Iterate over characters easily:
for ch in "Swift" {
print(ch)
}
Convert a String
to an array of Character
or vice versa:
let chars: [Character] = ["S", "w", "i", "f", "t"]
let word = String(chars) // "Swift"
Remember that characters may be composed of multiple scalars (e.g., é
vs. e
+ accent). Counting and indexing are grapheme-aware.
Use +
, +=
, or append(_:)
:
let part1 = "Hello"
let part2 = ", World"
let exclamation: Character = "!"
var message = part1 + part2
message.append(exclamation) // "Hello, World!"
For performance-heavy concatenation, consider String
’s reserveCapacity(_:)
or StringBuilder
-like patterns, but most apps won’t need them.
Interpolation inserts values right into your literals:
let count = 3
let item = "bug"
let line = "I fixed \(count) \(count == 1 ? item : item + "s")."
Swift 5.5+ lets you customize interpolation by extending String.StringInterpolation
. Advanced use cases include formatting dates or numbers with locale-aware style.
count
returns the number of Character
values, not bytes nor Unicode scalars:
let cafe = "café" // 4 characters
echo(cafe.count) // 4
Combining marks still count as part of one character. If you need the scalar count: cafe.unicodeScalars.count
.
Strings don’t support integer subscripts because grapheme clusters vary in length. Use indices:
let text = "Swift"
let start = text.startIndex
let second = text.index(after: start)
print(text[second]) // 'w'
Use index(_:offsetBy:)
to move forward or backward safely, and indices
to iterate over positions. Always respect startIndex
and endIndex
(which is one past the last valid character).
Insert characters or strings at any valid index:
var welcome = "Hello"
welcome.insert("!", at: welcome.endIndex) // Hello!
welcome.insert(contentsOf: ", Swift", at: welcome.index(before: welcome.endIndex)) // Hello, Swift!
Remove with remove(at:)
, removeSubrange(_:)
, or high-level helpers:
welcome.remove(at: welcome.index(before: welcome.endIndex)) // remove '!'
let range = welcome.firstIndex(of: ",")!..<welcome.endIndex
welcome.removeSubrange(range) // back to "Hello"
When working with Substring
, convert back to String
for long-term storage to free memory.
Swift supports value equality (==
), lexicographical comparison (<
, >
) and prefix/suffix checks:
let a = "straße"
let b = "strasse"
print(a == b) // false – different grapheme clusters
"Hello".hasPrefix("He") // true
"Hello".hasSuffix("lo") // true
Comparisons are locale-insensitive by default. If you need localized comparison or case folding, use Foundation’s localizedStandardCompare(_:)
or lowercased()
with a locale.
Q1. Why can’t I subscript a Swift String with an Int
index?
Because characters are variable-width Unicode grapheme clusters. Integer offsets could split a character in the middle. Use String.Index
APIs instead.
Q2. Is String
a value or reference type?
It’s a value type with copy-on-write semantics. You get predictable value behavior without unnecessary copies.
Q3. How do I efficiently build large strings?
Repeated concatenation is usually fine. For extreme cases, pre-allocate with reserveCapacity(_:)
or append to an array and join.
Q4. What’s the difference between Character
and UnicodeScalar
?
Character
is a human-readable grapheme cluster. UnicodeScalar
is a single 21-bit code point. One character may contain multiple scalars.
Q5. How do I compare strings ignoring case or diacritics?
Use Foundation helpers: caseInsensitiveCompare
, folding(options: .diacriticInsensitive, locale: ...)
, or localizedStandardCompare
for user-facing text.
Swift’s String
API balances correctness and ergonomics: you get Unicode safety without sacrificing speed. Mastering indices, interpolation, and mutation patterns will save you from subtle bugs.
Next steps:
Substring
and memory management.Regex
and string processing (Swift 5.7+).NavigationKit is a lightweight library which makes SwiftUI navigation super easy to use. 💻 Installation 📦 Swift Package Manager Using Swift Package Manager, add ...
An alternative SwiftUI NavigationView implementing classic stack-based navigation giving also some more control on animations and programmatic navigation. NavigationStack Installation ...
With SwiftUI Router you can power your SwiftUI app with path-based routing. By utilizing a path-based system, navigation in your app becomes ...
This package takes SwiftUI's familiar and powerful NavigationStack API and gives it superpowers, allowing you to use the same API not just ...