Post

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 and Characters in Swift

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.

This post is licensed under CC BY 4.0 by the author.