Strings
Swift’s String type is Unicode-correct by default — it stores text as a sequence of Extended Grapheme Clusters (user-perceived characters), not bytes or UTF-16 code units. The principal forms are double-quoted strings (admit interpolation and escapes) and multi-line strings (delimited by """). Strings admit substantial methods through the StringProtocol and the Sequence/Collection conformances. String interpolation via \(expression) admits embedded expressions; the mechanism is extensible via ExpressibleByStringInterpolation. Indexing uses String.Index rather than Int — admitting substantial Unicode correctness at the cost of some convenience. The combination — Unicode-correct grapheme clusters, the substantial method surface, multi-line literals, the extensible interpolation — is the substance of Swift’s text surface.
String literals
let single = "hello"
let multi = """
line one
line two
"""
let escaped = "with \"quotes\" and \n newlines"
let raw = #"raw \n string with no escapes"# // raw string (Swift 5+)
let rawWithInterp = #"hello \#(name)"# // raw with explicit interpolation
The """ form admits multi-line content; the indentation of the closing """ is stripped from each line:
let html = """
<html>
<body>
<p>Hello</p>
</body>
</html>
""" // 4-space indent stripped from each line
The conventional discipline:
- Double quotes for single-line strings.
"""for multi-line strings.#"..."#for strings with substantial backslashes (regex, paths).
String interpolation
let name = "Alice"
let age = 30
let greeting = "Hello, \(name)! You are \(age) years old."
let pi = "Pi: \(Double.pi.formatted(.number.precision(.fractionLength(2))))"
The \(expression) admits any expression. The compiler builds a String by calling each component’s description (via CustomStringConvertible).
For substantial format control, Swift 5.5+ admits formatted output:
import Foundation
let n = 1234567.89
n.formatted() // "1,234,567.89"
n.formatted(.number.precision(.fractionLength(2)))
n.formatted(.percent)
n.formatted(.currency(code: "USD"))
Date.now.formatted(date: .complete, time: .complete)
Characters and graphemes
A Character is a single extended grapheme cluster:
let s = "héllo"
s.count // 5 (graphemes)
// not 6 (bytes) or 5 (UTF-16 code units)
for c in s {
print(c) // h, é, l, l, o
}
let first: Character = "é" // single character
let manyCodePoints: Character = "👨👩👧" // also one Character (combining family)
The mechanism admits substantial Unicode correctness — what users perceive as one character is one Character, regardless of how many UTF code units it occupies.
Indexing
String indexing uses String.Index rather than Int:
let s = "hello"
let first = s[s.startIndex] // "h"
let second = s[s.index(after: s.startIndex)]
let last = s[s.index(before: s.endIndex)]
let i = s.index(s.startIndex, offsetBy: 2)
let third = s[i] // "l"
// Slicing:
let substring = s[s.startIndex..<i] // "he" (Substring)
let slice = s[s.index(s.startIndex, offsetBy: 1)...]
The index-by-Int form is not admitted directly — the underlying string is variable-width Unicode, so position-by-integer is not constant-time:
let third = s[2] // ERROR: Int not admitted as String.Index
For Int-style indexing, conversion via index(_:offsetBy:) admits substantial flexibility but with O(n) cost.
Substring
A Substring shares storage with its parent String:
let s = "hello world"
let prefix = s.prefix(5) // "hello" — Substring
let suffix = s.suffix(5)
let dropped = s.dropFirst(6) // "world"
// Convert to String:
let independent = String(prefix) // independent copy
The conventional discipline converts Substring to String for storage; substrings reference the parent and may keep substantial memory alive.
String methods
The substantial method surface:
let s = "hello world"
s.count
s.isEmpty
s.contains("ll") // true
s.hasPrefix("hello") // true
s.hasSuffix("world") // true
s.uppercased() // "HELLO WORLD"
s.lowercased()
s.capitalized // "Hello World"
s.trimmingCharacters(in: .whitespaces) // requires Foundation
s.replacingOccurrences(of: "world", with: "Swift") // requires Foundation
s.components(separatedBy: " ") // requires Foundation
// Splitting:
s.split(separator: " ") // [Substring]
s.split(separator: " ", maxSplits: 2)
s.split(separator: " ", omittingEmptySubsequences: false)
// Joining:
["a", "b", "c"].joined() // "abc"
["a", "b", "c"].joined(separator: ", ") // "a, b, c"
The Foundation framework adds substantial additional methods (regex, formatting, etc.).
Iteration
let s = "hello"
for char in s {
print(char) // h, e, l, l, o (Character each)
}
// As bytes (UTF-8):
for byte in s.utf8 {
print(byte)
}
// As UTF-16:
for unit in s.utf16 {
print(unit)
}
// As Unicode scalars:
for scalar in s.unicodeScalars {
print(scalar) // Unicode.Scalar
}
The principal views: String, String.UTF8View, String.UTF16View, String.UnicodeScalarView.
Conversion
// Number to String:
let s = String(42) // "42"
let s = String(3.14) // "3.14"
let s = "\(42)" // interpolation
let s = String(format: "%.2f", 3.14) // "3.14" (requires Foundation)
// String to number:
let n = Int("42") // Int? (returns nil on failure)
let d = Double("3.14") // Double?
let n = Int("abc") // nil
// Boolean:
String(true) // "true"
Bool("true") // Optional(true)
// Character to String:
let c: Character = "A"
let s = String(c)
The conventional defence for parsing is Optional returns; the conventional defence for formatting is interpolation or formatted().
Regular expressions (Swift 5.7+)
Since Swift 5.7, regular expressions are built in:
let s = "Date: 2026-01-15"
if let match = s.firstMatch(of: /(\d{4})-(\d{2})-(\d{2})/) {
let (full, year, month, day) = match.output
print("year: \(year), month: \(month), day: \(day)")
}
// Replace:
let cleaned = s.replacing(/\d+/, with: "X")
// Split:
let parts = s.split(separator: /\s+/)
The regex literal /.../is admitted directly; the RegexBuilder DSL (Swift 5.7+) admits substantial declarative regex construction:
import RegexBuilder
let dateRegex = Regex {
Capture { OneOrMore(.digit) }
"-"
Capture { OneOrMore(.digit) }
"-"
Capture { OneOrMore(.digit) }
}
For older Swift, the NSRegularExpression (from Foundation) is the conventional alternative.
Common patterns
Formatted strings
let total = 42
let price = 9.99
let report = """
Items: \(total)
Total: $\(String(format: "%.2f", price * Double(total)))
"""
Building a string
// Inefficient (each += allocates):
var result = ""
for item in items {
result += "\(item)\n"
}
// Efficient with += and explicit String(...):
var result = ""
for item in items {
result.append("\(item)\n")
}
// Or with reduce:
let result = items.map(String.init).joined(separator: "\n")
// Or with reserveCapacity for known sizes:
var s = String()
s.reserveCapacity(items.count * 10)
Multi-line for SQL/HTML
let sql = """
SELECT id, name, email
FROM users
WHERE active = TRUE
ORDER BY created_at DESC
"""
let html = """
<html>
<body>
<h1>\(title)</h1>
<p>\(content)</p>
</body>
</html>
"""
Parsing
import Foundation
let input = "42"
guard let n = Int(input) else {
throw ParseError.invalidNumber(input)
}
// With trimming:
let cleaned = input.trimmingCharacters(in: .whitespacesAndNewlines)
String slicing
let s = "hello world"
// First 5:
let first = s.prefix(5) // "hello"
// Last 5:
let last = s.suffix(5) // "world"
// Drop first/last:
let dropped = s.dropFirst() // "ello world"
let dropFirst3 = s.dropFirst(3) // "lo world"
let dropLast = s.dropLast(6) // "hello"
// Custom range:
let start = s.index(s.startIndex, offsetBy: 2)
let end = s.index(s.startIndex, offsetBy: 5)
let sub = String(s[start..<end]) // "llo"
Case-insensitive comparison
let a = "Hello"
let b = "hello"
a.lowercased() == b.lowercased() // true
a.caseInsensitiveCompare(b) == .orderedSame // requires Foundation
Word splitting
let words = "the quick brown fox".split(separator: " ")
// [Substring]: ["the", "quick", "brown", "fox"]
// With Foundation:
import Foundation
let words = "the quick brown fox".components(separatedBy: " ")
// [String]
Padding
// Left-pad:
String(repeating: "0", count: 5 - "42".count) + "42" // "00042"
// Or with format (Foundation):
String(format: "%05d", 42) // "00042"
Validation
import Foundation
let email = "alice@example.com"
let isValid = email.range(of: #"^[^\s@]+@[^\s@]+\.[^\s@]+$"#, options: .regularExpression) != nil
// Swift 5.7+:
let isValid = email.contains(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
Custom string interpolation
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: Date, format: String) {
let formatter = DateFormatter()
formatter.dateFormat = format
appendLiteral(formatter.string(from: value))
}
}
let formatted = "Today is \(Date.now, format: "yyyy-MM-dd")"
The mechanism admits substantial extensible interpolation — conventional in the Swift ecosystem.
A note on Unicode correctness
The principal Unicode features:
- Extended Grapheme Clusters — what users perceive as one character.
- Canonical equivalence — different code-point sequences may produce the same character.
- Comparison:
==compares by canonical equivalence, not by code units.
let a = "é" // single Unicode code point
let b = "e\u{0301}" // e + combining acute accent (two code points)
a.count // 1
b.count // 1 (one grapheme)
a == b // true (canonical equivalence)
a.unicodeScalars.count // 1
b.unicodeScalars.count // 2
The mechanism admits substantial correctness for international text; the cost is some performance (string operations are not constant-time).
A note on the conventional discipline
The contemporary Swift strings advice:
- Use double quotes for single-line strings.
- Use
"""for multi-line strings. - Use
#"..."#for raw strings (regex, paths). - Use string interpolation for formatting.
- Use
formatted()(Swift 5.5+) for substantial number/date formatting. - Use
prefix(_:),suffix(_:),dropFirst(_:),dropLast(_:)over manual indexing. - Use the regex literal
/.../(Swift 5.7+) for substantial regex. - Convert
SubstringtoStringfor storage. - Use
.utf8for byte-level operations. - Trust the Unicode correctness —
countreturns graphemes, not bytes.
The combination — Unicode-correct strings, the multi-line literal form, the substantial interpolation, the regex integration, the substantial Foundation extensions — is the substance of Swift’s text surface. The discipline produces clear, internationally-correct text handling with substantial built-in functionality.