Updating Strings For Swift 4

Another year passes and another set of changes to the Swift String API arrives. Swift 4 brings some much needed improvements and simplifications to String. These are my notes on what I needed to change to update my Swift String Cheat Sheet for Swift 4.

You can find the original and now updated guide and Xcode playground here:

String is a Collection Of Characters (Again)

This is a little frustrating. A String stopped being a collection in Swift 2.0. Now in Swift 4.0 it is back again. To recap in Swift 2 and 3 you needed to use a collection view. So to loop over characters you used the character view:

var sentence = "Never odd or even"
for character in sentence.characters {
  print(character)
}

Similar collection views exist for unicodeScalars, utf16 and utf8. This is still valid code in Swift 4 but is no longer necessary now that String is a Collection again. Looping over a String gives you the characters collection directly:

// Swift 4
let sentence = "Never odd or even"
for character in sentence {
  print(character)
}

Not needing to always refer to the characters collection makes using strings easier. For example a Swift 4 String now has a direct count property that gives the character count:

// Swift 4
let spain = "EspaƱa"
spain.count                         // 6

Compare this with the verbose Swift 3 way:

// The more verbose Swift 3 way still works
spain.characters.count              // 6 

If you want the count in a different encoding you can still use the view:

// Count using UTF8 encoding
spain.utf8.count                   // 7

Substrings

The other major change to strings with Swift 4 is with 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)
let indexEndOfText = template.index(template.endIndex, offsetBy: -3)

// 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 convert the Substring back to a String:

let string1 = String(substring1)

Prefer to use subscripting over the verbose prefix and suffix methods:

let digits = "0123456789"
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)

Multi-line String Literals

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, indent this text with
    two spaces.
  """

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 long line split \
    over two lines by escaping the newline.
    """

Further Reading

Swift Evolution Changes in Swift 4