Ranges in Swift explained with code examples

Learn about the 4 different range operators available

Thanks to Lukas for the great illustration

Ranges in Swift allow us to select parts of Strings, collections, and other types. They’re the Swift variant of NSRange which we know from Objective-C although they’re not exactly the same in usage, as I’ll explain in this blog post.

Ranges allow us to write elegant Swift code by making use of the range operator. Your first time working with them might be because you needed to select a range of characters from a String but there’s a lot more you can do with it!


Types of ranges

Closed range operator going from a…b

let range: ClosedRange = 0...10
print(range.first!) // 0
print(range.last!) // 10

A closed range operator going from a...b defines a range that includes both a and b in which a must not be greater than b.

The closed operator is useful if you’d like to use all the values. For example, if you’d like to iterate over all elements of a collection:

let names = ["Antoine", "Maaike", "Jaap"]
for index in 0...2 {
print("Name \(index) is \(names[index])")
}
// Name 0 is Antoine
// Name 1 is Maaike
// Name 2 is Jaap

The different types of operators can also be used to select elements from a collection. For this, however, we need to make use of the CountableClosedRange type:

let names = ["Antoine", "Maaike", "Jaap"]
let range: CountableClosedRange = 0...2
print(names[range]) // ["Antoine", "Maaike", "Jaap"]

Obviously, Swift is smart enough to detect the countable variant by itself. Therefore, you could write the above code as follows:

let names = ["Antoine", "Maaike", "Jaap"]
print(names[0...2]) // ["Antoine", "Maaike", "Jaap"]

Half-open range operator going from a..<b

let range: Range = 0..<10
print(range.first!) // 0
print(range.last!) // 9

A half-open range defines a range going from a to b but does not include b. It's named half-open as it's containing its first value but not its final value. Just like with the closed range, the value of a must not be greater than b.

The half-open operator can be used to iterate over zero-based lists such as arrays and collections in Swift in which you want to iterate up to but not including the length of the list. It’s basically the same as the earlier code example but now we can make use of the count property:

let names = ["Antoine", "Maaike", "Jaap"]
print(names[0..<names.count]) // ["Antoine", "Maaike", "Jaap"]

If we would’ve done the same with a closed operator we would run into the following error:

Fatal error: Array index is out of range

One-sided operator going from a…

let names = ["Antoine", "Maaike", "Jaap"]
print(names[...2]) // ["Antoine", "Maaike", "Jaap"]

Or taking all the elements starting from index 1 till the end of the array:

let names = ["Antoine", "Maaike", "Jaap"]
print(names[1...]) // ["Maaike", "Jaap"]

A one-sided range can be used for iteration but only if used with a starting value a.... Otherwise, it's unclear where the iteration should start. Iterating over a one-sided range requires you to manually check where the loop should end as it would otherwise continue indefinitely.

let neededNames = 2
var collectedNames: [String] = []
for index in 0... {
guard collectedNames.count != neededNames else { break }
collectedNames.append(names[index])
}
print(collectedNames) // ["Antoine", "Maaike"]

Converting a Range to an NSRange in Swift

let title = "A Swift Blog"
let range = title.range(of: "Swift")
let attributedString = NSMutableAttributedString(string: title)
attributedString.setAttributes([NSAttributedString.Key.foregroundColor: UIColor.orange], range: range) // Cannot convert value of type 'Range<String.Index>?' to expected argument type 'NSRange' (aka '_NSRange')

As Range can't be converted to NSRange we're running into the following error:

Cannot convert value of type ‘Range?’ to expected argument type ‘NSRange’ (aka ‘_NSRange’)

We can fix this by making use of the available convenience initializer of an NSRange that takes a Swift Range:

let convertedRange = NSRange(range, in: title)

The final code will look as follows:

let title = "A Swift Blog"
let range = title.range(of: "Swift")!
let convertedRange = NSRange(range, in: title)
let attributedString = NSMutableAttributedString(string: title)
attributedString.setAttributes([NSAttributedString.Key.foregroundColor: UIColor.orange], range: convertedRange)
print(attributedString)
// A {
// }Swift{
// NSColor = "UIExtendedSRGBColorSpace 1 0.5 0 1";
// } Blog{
// }

Ranges and Strings

let emojiText: NSString = "🚀launcher"
print(emojiText.substring(with: NSRange(location: 0, length: 2)))
// Expected: 🚀l
// Actually returns: 🚀

As you can see, the rocket emoji is more than 1 character long. Therefore, our substring is not returning the expected outcome.


Working with String indexes

let emojiText = "🚀launcher"
let endIndex = emojiText.index(emojiText.startIndex, offsetBy: 2)
let range: Range<String.Index> = emojiText.startIndex..<endIndex
print(emojiText[range]) // 🚀l

Conclusion

If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!


Originally published at SwiftLee.

More posts and updates: @twannl

Flawless iOS

🍏 Community around iOS development, mobile design, and marketing

Antoine van der lee 🇳🇱

Written by

iOS Developer @WeTransfer — Follow me on twitter.com/twannl for more tips & tricks — Blogging weekly at https://avanderlee.com — Clap & hold for a surprise!

Flawless iOS

🍏 Community around iOS development, mobile design, and marketing

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade