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.


Producing and maintaining these guides takes time and resources. If you found this article helpful and would like to support future content, consider a small contribution via Buy Me a Coffee. Contributions help cover hosting and creation costs and make it possible to keep publishing free, practical material. No pressure — sharing this post with your network or starring the project is equally appreciated. Thank you for reading.

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