Strings and Characters in Swift
Practical guide to Swift strings and characters - literals, mutability, interpolation, Unicode and grapheme clusters, indexing, substrings, and comparison techniques with copy-friendly examples.
Strings are sequences of user-perceived characters represented by the String type in Swift. This article explains string and character basics with small, copy-friendly examples.
String literals
Single-line string literals are written with double quotes. Use triple quotes for multi-line literals. Indentation rules are available to trim leading whitespace in multi-line strings.
1
2
3
4
5
6
let stringLiteral = "This is a string literal"
let multiLineLiteral = """
We can write multi-line strings.
Line breaks inside the triple quotes are preserved.
"""
Empty strings
You can create an empty string using a literal or the String initializer:
1
2
let emptyLiteral = ""
let emptyString = String()
Mutability
Use var for strings you plan to modify and let for constants.
1
2
3
var firstName = "Max"
let lastName = "Verstappen"
firstName += " " + lastName
The example above appends the last name to firstName. Attempting the same assignment to a let constant would cause a compile-time error.
Multiline string example and combining with a Character:
1
2
3
4
5
6
let multiLine = """
A
B
"""
let char: Character = "C"
let combined = multiLine + String(char)
The combined string now contains the three lines with C appended on a new line.
Iterating over characters
Iterate a string to access its Character values:
1
2
3
for ch in "Swift" {
print(ch)
}
Each loop iteration yields a Character (a user-perceived character), which may consist of one or more Unicode scalars.
String interpolation
Insert values into strings using \( ):
1
2
let points = 395.5
let statement = "Max Verstappen accumulated \(points) points"
Unicode and extended grapheme clusters
Each Character represents an extended grapheme cluster — one or more Unicode scalars that form a single user-perceived character. For example, “é” can be a single scalar (\u{E9}) or e plus a combining accent (\u{65}\u{301}); they are considered canonically equivalent.
1
2
let acuteE: Character = "\u{E9}"
let combinedE: Character = "\u{65}\u{301}"
The two Character values above compare as equal because they represent the same grapheme cluster.
Note: String.count counts extended grapheme clusters (user-perceived characters), not Unicode scalars or bytes.
1
2
3
4
var cafe = "cafe"
cafe.count
cafe += "\u{301}"
cafe.count
Here, cafe.count remains 4 after appending the combining accent because the result is still four user-perceived characters: c, a, f, é.
Accessing characters and indexes
Swift strings use String.Index instead of integer offsets because of variable-width Unicode characters.
1
2
3
4
let s = "Hello World!"
let start = s.startIndex
let first = s[start]
let fifth = s[s.index(start, offsetBy: 4)]
In the example above first is “H” and fifth is “o”. Note that s.endIndex represents the position after the last character and cannot be subscripted directly.
To iterate by index:
1
2
3
for idx in s.indices {
print(s[idx], terminator: "")
}
Modifying strings
You can insert, append, and remove characters or ranges using the String API.
1
2
3
4
var t = "Hello"
t.insert("!", at: t.endIndex)
t.insert(contentsOf: " World", at: t.index(t.startIndex, offsetBy: 5))
t.remove(at: t.index(before: t.endIndex))
After these operations t becomes first “Hello!”, then “Hello World!”, and finally “Hello World” after removing the trailing exclamation mark.
Substrings
Ranges of a String produce Substring (a String.SubSequence) which can share storage with the original string. Convert to String when you need an independent copy.
1
2
3
4
let hw = "Hello, World!"
let idx = hw.firstIndex(of: ",") ?? hw.endIndex
let sub = hw[..<idx]
let helloString = String(sub)
sub is a Substring view into hw containing “Hello”; helloString converts it to an owned String.
Comparing strings
String equality compares canonical equivalence of grapheme clusters.
1
2
let a = "Latt\u{E9}"
let b = "Latt\u{65}\u{301}"
The two strings above are considered equal because they represent the same user-perceived characters.
Be aware that characters from different scripts (Latin vs Cyrillic) are distinct even if they look similar:
1
2
let latinA: Character = "\u{41}"
let cyrillicA: Character = "\u{0410}"
latinA == cyrillicA is false — the code points are different.
Use hasPrefix(_:) and hasSuffix(_:) for common prefix/suffix checks.
1
2
3
let name = "Charles Leclerc"
name.hasPrefix("Charles")
name.hasSuffix("Leclerc")
These return true for the example above.