Feb 21, 2018

String Slicing in Swift

String slicing is somewhat verbose in Swift. If you're coming from another language, it's not immediately obvious how to do it at all.

Many of us end up cursing the screen after trying to get substrings using patterns from other languages. If you feel the same way, you may have run into something like this:

let str = "πŸ“– Reading πŸ“š is πŸ“™ FUNdamental! 😁"

let substr = str[0]
// error: 'subscript' is unavailable: cannot subscript String with an Int

let substr = str[0..<5]
// error: 'subscript' is unavailable: cannot subscript String with a CountableRange<Int>

Swift Strings use a non-integer index type for reasons of speed and safety:

  • Speed: because an index access will always be a constant-time operation.
  • Safety: because you're guaranteed to get a whole character and not, say, half of a 16-bit character.

I know what you're thinking: that's swell and all, but I just want to grab a range of characters. Here's how you do that.

String has a few methods to get a Substring using integer arguments:

    func prefix(Int) -> Substring
    func suffix(Int) -> Substring
    func dropFirst(Int) -> Substring
    func dropLast(Int) -> Substring

Here are some examples:

let str = "πŸ“– Reading πŸ“š is πŸ“™ FUNdamental! 😁"
str.prefix(1)
// πŸ“–

str.suffix(1)
// 😁

str.dropFirst(2).prefix(9) as Substring
// Reading πŸ“š

str.dropFirst(2).dropLast(2) as Substring
// Reading πŸ“š is πŸ“™ FUNdamental!

There some more methods that return indexes.

    func startIndex -> String.Index
    func index(after: String.Index) -> String.Index
    func index(String.Index, offsetBy: Int) -> String.Index

Here are some examples of those:

let str = "πŸ“– Reading πŸ“š is πŸ“™ FUNdamental! 😁"
let start = str.startIndex
let tenth = str.index(start, offsetBy: 10)
let eleventh = str.index(after: tenth)
let books = str[tenth..<eleventh]
// πŸ“š

Note that subscripting with string indexes is constant time, but creating the index can be linear time. So if you’re advancing through the string, or if you repeatedly access indexed values, it’s better to keep a current index position than to repeatedly advance from the beginning of the string.

In other words, this:

let start = str.startIndex
let tenth = str.index(start, offsetBy: 10)
let sixteenth = str.index(tenth, offsetBy: 6)
for i in 1...1000 {
    let books = str[tenth..<sixteenth]
    // πŸ“š is πŸ“™
}

is better than this:

for i in 1...1000 {
    let start = str.startIndex
    let tenth = str.index(start, offsetBy: 10)
    let sixteenth = str.index(tenth, offsetBy: 6)
    let books = str[tenth..<sixteenth]
    // πŸ“š is πŸ“™
}

or this:

for i in 1...1000 {
    let books = str.dropFirst(10).prefix(6) as Substring
    // πŸ“š is πŸ“™
}

Note that these are by no means a complete set of methods. If you come across any other useful methods or patterns of accessing substrings, let me know.