Strings and Characters

A String is an ordered collection of characters — and in Swift it's a fully Unicode-correct value type, not a thin wrapper around bytes. This chapter covers writing literals (including multiline and raw strings), interpolation, the Unicode model that makes counting and indexing precise, and how to slice, mutate, and compare text.

String literals

A string literal is a sequence of characters surrounded by double quotation marks. You can use a literal to give a constant or variable an initial value.

let someString = "Some string literal value"

Multiline string literals

For text spanning several lines, use a multiline string literal: three double quotation marks. The indentation of the closing delimiter sets the baseline; any indentation to the left of it is stripped, so you can format the source nicely without affecting the value. End a line with \ to suppress the line break.

let quotation = """
The White Rabbit put on his spectacles.
  "Where shall I begin," he asked.
"Begin at the beginning," the King said.
"""

Special characters and extended delimiters

String literals understand escape sequences: \0 (null), \\ (backslash), \t (tab), \n (newline), \r (carriage return), \", \', and Unicode scalars as \u{n} where n is hex.

let wiseWords = "\"Imagination is more important than knowledge\""
let sparkle = "\u{2728}"   // → ✨

To avoid escaping when your text is full of quotes or backslashes, use extended delimiters: place one or more # around the literal. Inside, escapes are inert unless you match the # count.

let raw = #"Line with a \n that is NOT a newline"#
let stillEscaped = #"A real newline: \#n here"#

Empty strings and value semantics

Create an empty string with an empty literal or the initializer; check emptiness with isEmpty. Strings are value types: assigning or passing a string copies it, so each piece of code works with its own independent value.

var empty1 = ""
var empty2 = String()
print(empty1.isEmpty)   // → true

var original = "hello"
var copy = original
copy += "!"
print(original)   // → hello  (the copy is independent)

Note: Swift's copy-on-write optimization means a copy is only physically made when one of the strings is mutated, so value semantics stay cheap.

Working with characters

A string is a collection of Character values. You can iterate over a string with a for-in loop, build a string from an array of characters, or append a single character.

for ch in "Dog!🐶" {
    print(ch)   // → D  o  g  !  🐶  (one per line)
}

let exclamation: Character = "!"
let chars: [Character] = ["C", "a", "t"]
var word = String(chars)
word.append(exclamation)
print(word)   // → Cat!

Concatenation and interpolation

Combine strings with +, append in place with += or append(_:), and build strings inline with interpolation — a backslash and parentheses, \(expression), that evaluates any expression and inserts its textual form.

let greeting = "Hello" + ", " + "world"
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
print(message)   // → 3 times 2.5 is 7.5

Inside extended delimiters, interpolation also needs the matching #: #"Result: \#(value)"#.

Unicode

Swift's strings are Unicode-correct from the ground up. The unit you iterate over — a Character — is an extended grapheme cluster: a sequence of one or more Unicode scalars that together render as a single human-perceived character. This is why an accented letter or a flag emoji counts as one character even when it's built from several scalars.

// é can be one scalar, or 'e' plus a combining accent — both are one Character
let precomposed: Character = "\u{E9}"          // é
let decomposed: Character = "\u{65}\u{301}"     // e + ́  → é
print(precomposed == decomposed)   // → true  (canonical equivalence)

You can drop down to the underlying views when you need them: unicodeScalars for the 21-bit scalar values, plus utf8 and utf16 for code-unit access.

let dog = "Dog‼🐶"
for scalar in dog.unicodeScalars {
    print(scalar.value, terminator: " ")
}
// → 68 111 103 8252 128054

Counting characters

Because characters can span a variable number of scalars, count walks the string to count grapheme clusters. It reflects what a reader would count — combining a character with an accent doesn't change the count.

var word = "cafe"
print(word.count)   // → 4

word += "\u{301}"   // add a combining acute accent to the final e
print(word)         // → café
print(word.count)   // → 4  (still four characters)

Watch out: Because characters are variable-width, you can't index a string with an integer like str[2]. Swift uses opaque String.Index values instead, which is what the next section is about.

Indexing, inserting, and removing

Each string exposes startIndex, endIndex (one past the last character), and methods to move between positions: index(after:), index(before:), and index(_:offsetBy:). Insert and remove with the family of mutating methods.

let greeting = "Guten Tag!"
print(greeting[greeting.startIndex])                      // → G
print(greeting[greeting.index(before: greeting.endIndex)]) // → !
let i = greeting.index(greeting.startIndex, offsetBy: 6)
print(greeting[i])                                        // → T

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
print(welcome)   // → hello!

welcome.remove(at: welcome.index(before: welcome.endIndex))
print(welcome)   // → hello

let range = welcome.index(welcome.endIndex, offsetBy: -3)..<welcome.endIndex
welcome.removeSubrange(range)
print(welcome)   // → he

Substrings

When you slice a string — for example with prefix(_:) or a subscript range — you get a Substring, not a String. A substring shares storage with its original for efficiency, so it's meant for short-lived work. Convert it to a String when you need to store it for the long term.

let greeting = "Hello, world!"
let comma = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<comma]   // a Substring → "Hello"

// Promote to a standalone String for long-term storage
let newString = String(beginning)

Comparison: equality, prefix, and suffix

Compare strings and characters with == and !=. Comparison is based on canonical equivalence, so two strings that look identical to a reader are equal even if assembled from different scalars. Use hasPrefix(_:) and hasSuffix(_:) to test the beginning or end of a string.

let a = "We're a lot alike, you and I."
let b = "We're a lot alike, you and I."
print(a == b)   // → true

let scene = "Act 1 Scene 2: Capulet's mansion"
print(scene.hasPrefix("Act 1"))      // → true
print(scene.hasSuffix("mansion"))    // → true

Tip: For case-insensitive comparison, normalize both sides first with lowercased() (or uppercased()) before comparing.

Wrapping up

You've seen how Swift models text: literals (single-line, multiline, and raw), interpolation, the Unicode-correct character model with its scalar/UTF views, accurate counting, opaque indexing with insert and remove, efficient substrings, and equality and prefix/suffix comparison. Next we group values together with Swift's collection types.