Back to blog

Ranges in Swift Explained


Aasif Khan
By Aasif Khan | Last Updated on April 28th, 2022 6:43 am | 3-min read

You use ranges in Swift to define values between a lower and upper limit. Ranges are useful for creating slices of arrays, checking if a value is contained in a range, and much more.

In this tutorial, we’ll discuss how you can work with ranges in Swift. We’ll discuss ranges vs. arrays, types of ranges, working with ranges and strings, and tips and tricks for practical iOS development.

Types of Ranges in Swift

First things first. What’s a range? A range in Swift defines stuff – usually intgers – between a lower and upper limit. It’s the same as that we’re saying: “A zoo has animals ranging from aardvarks to zebras.” Tigers and dolphins fit in that range.

Here’s an example of a range in Swift:

let allAges = 0…99
print(allAges.contains(42))

In the above code, we’ve used the closed range operator … to define a range from 0 to 99, including 99. The range contains the number 42, for example.

Swift has a few range operators:

  1. Closed ranges with a…b
  2. Half-open ranges with a..
  3. One-sided ranges with a…, …b and ..

Closed Ranges

The closed range operator a…b is the simplest. It contains every item between the lower bound, often called a, and the upper bound, often called b, including b. It’s what we mean when we say: “0 to 99, including 99.”

Here’s an example:

let numbers = 0…10

for number in numbers {
print(number)
}

The above code uses the … operator to print every number from 0 to 10 in a for loop. The type of numbers in the above example is ClosedRange. This is a generic; we’ve created a value of type ClosedRange that uses integer numbers.

Behind the scenes, the above for loop uses a different type called CountableClosedRange. This is an alias for ClosedRange, which makes the range “strideable”. It’s just a fancy way of saying: you can iterate over this range. Good to know!

Half-Open Ranges

The half-open range operator a..b is the other range operator you’ll often work with. Instead of …, this operator does not include its upper bound in the range. So a range of 0..<99 does not include 99 in the range. Here’s an example: let numbers = 0..<5 print(Array(numbers)) The above code prints [0, 1, 2, 3, 4], so you’ll see that the 0..<5 range does not include the number 5. That’s because of the half-open range operator ..<. Perhaps you’re wondering why we have 2 different operators for essentially the same thing. If you don’t want the last item of a range, why don’t you stop the range one item earlier? A range 0...99 is the same as 0..<100, right? As it turns out, much of the work with ranges happens around the edges of things. For example, an array’s last index number is the same as its length minus one. We can either limit the range with a calculation, i.e. 0...array.count - 1, or we can use the half-open range operator: 0..

Working with Ranges and Arrays

Let’s put those ranges to use! The first use case we’ll discuss is the most common one: ranges and arrays. In practical app development, you often use ranges in conjunction with arrays.

let names = [“Bernard”, “Dolores”, “Maeve”, “Lee”]

for i in 0..Array Slicing with Ranges

That’s not all ranges are good for. We can use range operators to extract sections of an array, called a slice. Check this out:

let names = [“Ford”, “Arthur”, “Zaphod”, “Marvin”, “Eddie”]
let slice = names[0…2]
print(slice)

In the above code, we’ve defined an array of strings called names. With the closed range operator … we’re “selecting” a subsection of that array, namely the items 0, 1 and 2. The resulting slice is printed out: [“Ford”, “Arthur”, “Zaphod”]. Neat!

It’s worth noting here that the type of the slice constant is ArraySlice. It’s not an array, nor is it a copy of the names array. Instead, a slice is an ephemeral “view” into the source names array.

You could see a slice as an index-only copy of the array, which references the old names array. Swift automatically transforms the slice to an actual Array type if needed, or you can use the Array() initializer yourself.

One-Sided Ranges

Let’s take that one step further with the third type of range: a one-sided range. They work the same as the closed and half-open ranges, except that they either don’t have a lower bound, or don’t have an upper bound.

This results in the following one-sided range operators:

  • a… includes everything from a to the end of the range, including a itself
  • …b includes everything from the beginning of the range to b
  • ..b includes everything from the beginning of the range to b, not including b

See how that works? You only define one limit — and everything up to or from that limit. Here’s an example:

let names = [“Ford”, “Arthur”, “Zaphod”, “Marvin”, “Eddie”]
print(names[3…]) // [“Marvin”, “Eddie”]
print(names[…2]) // [“Ford”, “Arthur”, “Zaphod”]
print(names[..<2]) // ["Ford", "Arthur"] Keep in mind that slices and one-sided ranges are just constructs. They define a view into a dataset that does not include a hardcopy of that dataset. For example, if you were to a define a freeform range like 1..., it doesn’t include every hard-coded integer number from 1 to infinity. Instead, you apply it onto your dataset, and it gives you every item from 1 to the end of your dataset.

Using Strings with Ranges

Strings in Swift are funky. In many programming languages, you can deal with strings as if they are arrays of characters. That doesn’t work in Swift. Check this out:

let name = “Reinder”
print(name[2])
You’d expect the above code to output “i”, which is the 3rd character in the string. It doesn’t work that way, for a few reasons, the most important being that strings in Swift are encoded as UTF-8 and that’s a variable width encoding.

In short, a character in Swift doesn’t have a fixed width so you can’t calculate the index of an arbitrary character in a string. When you say “Get me the 7th character!” you might end up between characters 5 and 9, depending on the length of the individual characters in the string.

Swift solves this problem by providing APIs for strings that work with indices. You can “walk” or “stride” these indices, increasing them in steps. Swift figures out whether a next index involves stepping over one or multiple bytes. Once you’ve created indices, you can use regular range operators.

Take a look at this example:

let text = “The lazy dog jumped over the quick brown fox”

let start = text.index(text.startIndex, offsetBy: 4)
let end = text.index(text.startIndex, offsetBy: 8)

print(text[start..

Pro Tips and Tricks

Let’s discuss a few things to keep in mind when working with ranges. First, it’s important to be mindful of out-of-bounds errors that might occur. Then, we’ll take a look at the ~= pattern matching operator.

Off-by-One Errors
Check this out:

let names = [“Ford”, “Arthur”, “Zaphod”, “Marvin”, “Eddie”]

for i in 0…names.count {
print(names[i])
}

Looks like OK Swift code, right? It contains a sneaky error though, called off-by-one (OBOE). When you run it, you get this output:

Fatal error: Index out of range

See that 0…names.count code? This will iterate from 0 to the end of the array, except… the index of the last item of the array is equal to names.count – 1. The above range will loop from 0 to 5, including 5, and 5 is not a valid index of the array. The last index is 4, of course!

It’s easy to make off-by-one errors when working with ranges. You can deal with the errors as they occur, or be mindful of the edges of your ranges and collections as you work with them.

Pattern Matching Operator with Ranges

Here’s one last cool thing you can do with ranges:

let statusCode = 403

if 400..<500 ~= statusCode { print("Oops!") } In the above code, we’ve defined a statusCode constant with a value 403. Then, we’re checking if this value is contained within the range 400..<500 by using the ~= pattern matching operator in the conditional. It’s essentially the same as (400..<500).contains(statusCode), but it’s more concise. You can use the pattern matching operator in switch statements too, to select a range of values instead of a single value. Neat!

Further Reading

We’ve looked at how you can use ranges in Swift in this tutorial. Here’s what we discussed:

  • Types of ranges and range operators in Swift, like ..<
  • Working with ranges and arrays, like creating a slice
  • Working with ranges and strings, like creating indices
  • Why you should watch out for off-by-one errors
  • Cool stuff like pattern matching with ranges and ~=


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts