Swift String Cheat Sheet

The Swift String API is hard to get used to. It has also changed over time as the Swift language and the standard library have developed. I first wrote this guide for Swift 2 and have since needed to update it for Swift 3, 4 and now Swift 5. So for my future reference and yours if you are struggling to make sense of it all here is my Swift String Cheat Sheet:

Last updated: Nov 10, 2022

Xcode Playground

You can get the latest version of the Xcode playground from my GitHub repository:

Version History

See the following posts for the main changes since I first wrote this guide for Swift 2:

Initializing A String

There are an almost endless number of ways to create a String, using literals, conversions from other Swift types, Unicode, etc.

var emptyString = ""            // Empty (Mutable) String
let stillEmpty = String()       // Another empty String
let helloWorld = "Hello World!" // String literal

let a = String(true)            // from boolean: "true"
let b: Character = "A"          // Explicit type to create a Character
let c = String(b)               // from character "A"
let d = String(3.14)            // from Double "3.14"
let e = String(1000)            // from Int "1000"
let f = "Result = \(d)"         // Interpolation "Result = 3.14"
let g = "\u{2126}"              // Unicode Ohm sign Ω

// New in Swift 4.2
let hex = String(254, radix: 16, uppercase: true) // "FE"
let octal = String(18, radix: 8) // "22"

Creating A String With Repeating Values

let h = String(repeating:"01", count:3) // 010101

Creating A String From A File

The file is in the playground Resources folder.

if let txtPath = Bundle.main.path(forResource: "lorem", ofType: "txt") {
  do {
    let lorem = try String(contentsOfFile: txtPath, encoding: .utf8)
  } catch {
    print("Something went wrong")
  }
}

Multi-line String Literals (Swift 4)

Swift now allows you to create a multi-line String literals. You wrap the strings in triple double quotes ("""String"""). You do not need to escape newlines and quotes within the string:

let verse = """
    To be, or not to be - that is the question;
    Whether 'tis nobler in the mind to suffer
    The slings and arrows of outrageous fortune,
    Or to take arms against a sea of troubles,
    """

You can control the leading white space with the indentation of the text relative to the closing """. In the last example there is no leading whitespace in the final string literal. In this next example we indent the text by two spaces:

let indentedText = """
    Hello, this text is indented by
    two spaces from the closing quotes
  """

Source code with overly long string literals can be hard to read. To split long lines in the source use a \ to escape the new line.

let singleLongLine = """
    This is a single longe line split \
    over two lines by escaping the newline.
    """

Creating Strings From Raw Text (Swift 5)

When creating strings from raw text you can customise the delimiter and escape characters. Using the default delimiter (double-quote) and escape sequence (backslash) to create a String you might write:

let title = "Insert \"title\" here"
// Insert "title" here

Swift 5 allows you to pad the delimiter and escape sequence with one or more #. We can write the previous example as follows:

let title2 = #"Insert "title" here"#
let title3 = ##"Insert "title" here"##
let title4 = ###"Insert "title" here"###
// Insert "title" here

Note that we don’t need to escape the double-quotes now as they are no longer a delimiter. If our raw text contains our chosen delimiter we can pad with an extra “#“:

// raw text is "#Hello#"
// start delimiter is ##"
// end delimiter is "##
let regex1 = ##""#Hello#""##       // "#Hello#"

If we pad the delimiters with one or more #’s, we also need to pad the backslash escape sequence. For example, when interpolating a value:

let name = "Tom"
let greeting1 = "Hello \(name)"    // Hello Tom

When padding with a single # the escape sequence becomes \#:

let greeting2 = #"Hello \#(name)"# // Hello Tom

Custom delimiters become useful when we want to preserve escaped raw text. For example, when creating a String from some JSON. Using a multi-line String literal seems like a good approach:

let json1 = """
{
  "colors": ["red","green","blue"],
  "label": "Insert \"title\" here"
}
"""

The multi-line string literal is convenient when the text contains quotes, but it introduces an error in this case. The problem is that the compiler strips the backslash escaping around “title” resulting in some invalid JSON:

{
   "colors": ["red","green","blue"],
   "label": "Insert "title" here"
}

If we use a custom delimiter with multi-line string literals we can keep the escape sequence in the raw text:

let json2 = #"""
{
  "colors": ["red","green","blue"],
  "label": "Insert \"title\" here"
}
"""#

The resulting String with the preserved raw text (note the backslash-escaped double-quotes aroung title):

{
   "colors": ["red","green","blue"],
   "label": "Insert \"title\" here"
}

Strings Are Value Types

Strings are value types (a Struct) that are copied when assigned or passed to a function. The copy is performed lazily on mutation.

var aString = "Hello"
var bString = aString
bString += " World!"    // "Hello World!"
print("\(aString)")     // "Hello\n"

Testing For Empty

let name = ""
name.isEmpty         // true

let title = String()
title.isEmpty        // true

Testing For Equality

Swift is Unicode correct so the equality operator ("==") checks for Unicode canonical equivalence. This means that two Strings that are composed from different Unicode scalars will be considered equal if they have the same linguistic meaning and appearance:

let spain = "España"
let tilde = "\u{303}"
let country = "Espan" + "\(tilde)" + "a"
if country == spain {
  print("Matched!")       // "Matched!\n"
}

Comparing For Order

if "aaa" < "bbb" {
  print("aaa is less than bbb")  // "aaa is less than bbb"
}

Testing For Suffix/Prefix

let line = "0001 Some test data here %%%%"
line.hasPrefix("0001")    // true
line.hasSuffix("%%%%")    // true

Converting To Upper/Lower Case

let mixedCase = "AbcDef"
let upper = mixedCase.uppercased() // "ABCDEF"
let lower = mixedCase.lowercased() // "abcdef"

Character Collections

In Swift 4 strings are back to being collections of characters. You can access different representations of the string through the appropriate collection view.

country.unicodeScalars   // Unicode scalar 21-bit codes
country.utf16            // UTF-16 encoding
country.utf8             // UTF-8 encoding

Strings Are Collections Of Characters (Swift 4)

A String is now a collection of characters by default so iterating over a String or Substring gives you each character in the `String:

// Swift 4
for character in country {
    print(character)
}

To get the first or last character in a String. The result is an optional returning nil if the String is empty.

country.first // "E"
country.last  // "a"

Random Element and Shuffle

Swift 4.2 allows you to get a random element from any collection. When used on a String you get a random character or nil if the String is empty:

let suits = "♠︎♣︎♥︎♦︎"
suits.randomElement()

Iterate over shuffled String

for suit in suits.shuffled() {
  print(suit)
}

Counting

Count is implemented for each of the collection views as it is dependent on the representation. The count property of a String is the character count.

// spain = España
spain.count                 // 6
spain.unicodeScalars.count  // 6
spain.utf16.count           // 6
spain.utf8.count            // 7

Character Properties

Swift 5 adds convenient character properties. Definitions are from the Unicode standards.

Note: These proprerties operate on characters, not strings.

Testing For ASCII

Test if a character is ASCII (note that these properties operate on characters not strings):

let a = "A" as Character
let pi = "π" as Character
a.isASCII               // true
pi.isASCII              // false

The asciiValue property is an optional integer that returns the ASCII value (or nil if the character is not ASCII):

a.asciiValue            // Int? (65)
pi.asciiValue           // nil

Testing For Whitespace and New Lines

The isWhitespace property tests for spaces and other separator characters:

let tab = "\t" as Character
tab.isWhitespace        // true

A new line character is also classed as whitespace. The isNewline property tests more specifically for it (and other line separators):

let newline = "\n" as Character
newline.isWhitespace    // true
newline.isNewline       // true

Testing For Numbers

Test for numbers and whole numbers:

let five = "5" as Character
let half = "½" as Character
five.isNumber           // true
half.isNumber           // true

If the character is a whole number then wholeNumberValue gives you the numeric value:

five.isWholeNumber      // true
five.wholeNumberValue   // Int? (5)
half.isWholeNumber      // false
half.wholeNumberValue   // nil

This also works for hexadecimal characters (upper or lower case):

let a = "A" as Character
a.isHexDigit            // true
a.hexDigitValue         // Int? (10)

Testing For Letters

Does the character represent an alphabetic letter:

a.isLetter              // true
pi.isLetter             // true (Greek alphabet)

let scream = "😱" as Character
scream.isLetter         // false

Testing For Symbols

Test if a character is a symbol:

let smiley = "😀" as Character
smiley.isSymbol         // true
smiley.isLetter         // false

let plus = "+" as Character
plus.isSymbol           // true
plus.isLetter           // false

Test for a math symbol:

plus.isMathSymbol       // true
smiley.isMathSymbol     // false

Test for a currency symbol:

let dollar = "$" as Character
dollar.isCurrencySymbol // true

Punctuation

To test for punctuation marks:

let qmark = "?" as Character
qmark.isPunctuation     // true

Upper And Lower Case

Properties to test for case and functions to convert the case:

let b = "b" as Character
let z = "Z" as Character
b.isLowercase           // true
z.isUppercase           // true

The functions to convert to upper or lower case return a String as it can result in multiple characters:

b.uppercased()          // B
pi.uppercased()         // Π
z.lowercased()          // z

let sharpS = "ß" as Character
sharpS.uppercased()     // SS

The isCased property is a strange one. It tests if the character changes when converted to upper or lower case:

z.isCased               // true (z or Z)
b.isCased               // true (b or B)

let half = "½" as Character
half.isCased            // false (always ½)

Using Index To Traverse A Collection

Each of the collection views has an Index that you use to traverse the collection. This is maybe one of the big causes of pain when getting to grips with String. You cannot randomly access an element in a string using a subscript (e.g. string[5]).

Each collection has two instance properties you can use as subscripts to index into the collection:

  • startIndex: the position of the first element if non-empty, else identical to endIndex.
  • endIndex: the position just “past the end” of the string.

When used directly with a String or Substring you get an index into the character view:

let hello = "hello"
let startIndex = hello.startIndex // 0
let endIndex = hello.endIndex     // 5
hello[startIndex]                 // "h"

Note the choice for endIndex means you cannot use it directly as a subscript as it is out of range.

Use index(after:) and index(before:) to move forward or backward from an index:

hello[hello.index(after: startIndex)] // "e"
hello[hello.index(before: endIndex)]  // "o"

Use index(_:offsetBy:) to move in arbitrary steps. A negative offset moves backwards:

hello[hello.index(startIndex, offsetBy: 1)]  // "e"
hello[hello.index(endIndex, offsetBy: -4)]   // "e"

You can also limit the offset to avoid an error when you run off the end of the index. The function index(_:offsetBy:limitedBy:) returns an optional which will be nil if you go too far:

if let someIndex = hello.index(startIndex,
                   offsetBy: 4, limitedBy: endIndex) {
  hello[someIndex] // "o"
}

Using the utf16 view:

let cafe = "café"
let view = cafe.utf16
let utf16StartIndex = view.startIndex
let utf16EndIndex = view.endIndex

view[utf16StartIndex]                          // 99 - "c"
view[view.index(utf16StartIndex, offsetBy: 1)] // 97 - "a"
view[view.index(before: utf16EndIndex)]        // 233 - "é"

The indices property returns a range for all elements in a String that can be useful for iterating through the collection:

for index in cafe.indices {
  print(cafe[index])
}

You cannot use an index from one string to access a different string. You can convert an index to an integer value with the distance(from:to:) method:

let word1 = "ABCDEF"
let word2 = "012345"
if let indexC = word1.firstIndex(of: "C") {
  let distance = word1.distance(from: word1.startIndex, to: indexC) // 2
  let digit = word2[word2.index(startIndex, offsetBy: distance)]    // "2"
}

Finding Matches

The Sequence and Collection methods for finding the first and last element and index of an element that matched a predicate all work with String:

Contains

Testing if a String contains another String

let alphabet = "abcdefghijklmnopqrstuvwxyz"
alphabet.contains("jkl")  // true

Finding First Or Last Match

To find the index of the first matching element (but note that the return value is an optional):

let k = alphabet.first { $0 > "j" }  // "k"

if let matchedIndex = alphabet.firstIndex(of: "x") {
  alphabet[matchedIndex]  // "x"
}

let nomatchIndex = alphabet.firstIndex(of: "A") // nil

if let nextIndex = alphabet.firstIndex(where: { $0 > "j" }) {
  alphabet[nextIndex]  // "k"
}

Swift 4.2 also adds equivalent methods to find the last element:

let lastMatch = alphabet.last { $0 > "j" } // "z"

if let lastX = alphabet.lastIndex(of: "x") {
  alphabet[lastX] // "x"
}

if let lastIndex = alphabet.lastIndex(where: { $0 > "j" }) {
  alphabet[lastIndex] // "z"
}

Using A Range

To identify a range of elements in a string collection use a range. A range is just a start and end index:

let fqdn = "useyourloaf.com"
let tldEndIndex = fqdn.endIndex
let tldStartIndex = fqdn.index(tldEndIndex, offsetBy: -3)
let range = Range(uncheckedBounds: (lower: tldStartIndex, upper: tldEndIndex))
fqdn[range]    // "com"

Creating A Range With ... Or ..< Operators

let endOfDomain = fqdn.index(fqdn.endIndex, offsetBy: -4)
let rangeOfDomain = fqdn.startIndex ..< endOfDomain
fqdn[rangeOfDomain] // useyourloaf

Returning The Range Of A Matching Substring

To return the range of a matching substring or nil if not found:

if let rangeOfTLD = fqdn.range(of: "com") {
  let tld = fqdn[rangeOfTLD] // "com"
}

Substrings

When you slice a string in Swift 4 you do not get back a String you get a Substring. A Substring has most of the same methods as a String (it conforms to StringProtocol) which makes life easy.

A Substring shares the storage of the original string. For this reason you should treat it a temporary object. From The Swift Programming Language (Swift 4):

substrings aren’t suitable for long-term storage – because they reuse the storage of the original string the entire original string must be kept in memory as long as any of its substrings are being used.

If you want to store it or pass it around convert it back to a String.

In Swift 4 you slice a string into a substring using subscripting. The use of substring(from:), substring(to:) and substring(with:) are all deprecated.

To get a substring from an index up to the end of the string:

let template = "<<<Hello>>>"
let indexStartOfText = template.index(template.startIndex, offsetBy: 3) // 3
let indexEndOfText = template.index(template.endIndex, offsetBy: -3)    // 8

// Swift 4
let substring1 = template[indexStartOfText...]  // "Hello>>>"

// Swift 3 deprecated
// let substring1 = template.substring(from: indexStartOfText)

To get a substring from the start of the string up to an index:

// Swift 4
let substring2 = template[..<indexEndOfText]    // "<<<Hello"

// Swift 3 deprecated
// let substring2 = template.substring(to: indexEndOfText)

To get a range within the String

// Swift 4
let substring3 = template[indexStartOfText..<indexEndOfText] // "Hello"

// Swift 3 deprecated
// let substring3 = template.substring(with: indexStartOfText..<indexEndOfText)

To directly get the range:

if let range3 = template.range(of: "Hello") {
  template[range3] // "Hello"
}

Converting A Substring Back To A String

Use the String() initializer to convert back to a String.

let string1 = String(substring1)

Getting A Prefix Or Suffix

If you just need to drop/retrieve elements at the beginning or end of a String. These all return a Substring - use the String() initializer if you need to convert back to a String:

let digits = "0123456789"
let tail = digits.dropFirst()  // "123456789"
let less = digits.dropFirst(3) // "3456789"
let head = digits.dropLast(3)  // "0123456"

let prefix = digits.prefix(2)  // "01"
let suffix = digits.suffix(2)  // "89"

With Swift 4, prefer to use subscripting over the verbose prefix and suffix methods:

let index4 = digits.index(digits.startIndex, offsetBy: 4)

// The first of each of these examples is preferred
digits[...index4]               // "01234"
digits.prefix(through: index4)

digits[..<index4]               // "0123"
digits.prefix(upTo: index4)

digits[index4...]               // "456789"
digits.suffix(from: index4)

Insert A Character At Index

var stars = "******"
stars.insert("X", at: stars.index(stars.startIndex, offsetBy: 3)) // "***X***"

Replace With Range

var stars = "***XYZ***"
if let xyzRange = stars.range(of: "XYZ") {
  stars.replaceSubrange(xyzRange, with: "ABC") // "***ABC***"
}

Append

You concatenate strings with the + operator or the append method:

var message = "Welcome"
message += " Tim" // "Welcome Tim"
message.append("!!!") // "Welcome Tim!!!

Remove And Return Element At Index

This invalidates any indices you may have on the string.

var grades = "ABCDEF"
let ch = grades.remove(at: grades.startIndex) // "A"
print(grades)                                 // "BCDEF"

Remove Range

Invalidates all indices.

var sequences = "ABA BBA ABC"
let lowBound = sequences.index(sequences.startIndex, offsetBy: 4)
let hiBound = sequences.index(sequences.endIndex, offsetBy: -4)
let midRange = lowBound ..< hiBound
sequences.removeSubrange(midRange) // "ABA ABC"

Bridging To NSString

String is bridged to Objective-C as NSString. If the Swift Standard Library does not have what you need import the Foundation framework to get access to methods defined by NSString.

Be aware that this is not a toll-free bridge so stick to the Swift Standard Library where possible.

Don’t forget to import Foundation

import Foundation
let welcome = "hello world!"
welcome.capitalized          // "Hello World!"

Searching For A Substring

An example of using NSString methods to perform a search for a substring:

let text = "123045780984"

// Find last occurance of "0"
if let rangeOfZero = text.range(of: "0", options: .backwards) {
  // Get the characters after the last 0
  let suffix = String(text.suffix(from: rangeOfZero.upperBound)) // "984"
  print(suffix)
}

Further Reading

Swift evolution changes in Swift 5

Swift evolution changes in Swift 4.2:

Swift Evolution Changes in Swift 4

Swift Evolution Changes in Swift 3