Swift Tutorial Part 3: Flow Control

Welcome to part 3 of our Swift tutorial, where you’ll learn how code decisions using Booleans and repeat tasks using loops to control the flow. By Lorenzo Boaro.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 5 of this article. Click here to view the first page.

Switch Statements

Another way to control flow is through the use of a switch statement, which lets you execute different bits of code depending on the value of a variable or constant.

Here’s a very simple switch statement that acts on an integer:

let number = 10

switch number {
case 0:
  print("Zero")
default:
  print("Non-zero")
}

The code will print the following:

Non-zero

The purpose of this switch statement is to determine whether or not a number is zero.

To handle a specific case, you use case, followed by the value that you want to check for, which, in this case, is 0. Then, you use default to signify what should happen for all other values.

Here’s another example:

switch number {
case 10:
  print("It's ten!")
default:
  break
}

This time you check for 10, in which case, you print a message. Nothing should happen for other values. When you want nothing to happen for a case, or you want the default state to run, you use the break statement. This tells Swift that you meant to not write any code here and that nothing should happen. Cases can never be empty, so you must write some code, even if it’s just a break!

switch statements work with any data type! Here’s an example of switching on a string:

let string = "Dog"

switch string {
case "Cat", "Dog":
  print("Animal is a house pet.")
default:
  print("Animal is not a house pet.")
}

This will print the following:

Animal is a house pet.

In this example, you provide two values for the case, meaning that if the value is equal to either "Cat" or "Dog" then the statement will execute the case.

Advanced Switch Statements

You can also give your switch statements more than one case. In the previous section, you saw an if statement using multiple else-if statements to convert an hour of the day to a string describing that part of the day. You could rewrite that more succinctly with a switch statement, like so:

let hourOfDay = 12
var timeOfDay = ""

switch hourOfDay {
case 0, 1, 2, 3, 4, 5:
  timeOfDay = "Early morning"
case 6, 7, 8, 9, 10, 11:
  timeOfDay = "Morning"
case 12, 13, 14, 15, 16:
  timeOfDay = "Afternoon"
case 17, 18, 19:
  timeOfDay = "Evening"
case 20, 21, 22, 23:
  timeOfDay = "Late evening"
default:
  timeOfDay = "INVALID HOUR!"
}

print(timeOfDay)

This code will print the following:

Afternoon

Remember ranges? Well, you can use ranges to simplify this switch statement. You can rewrite it in a more succinct and clearer way using ranges as shown below:

var timeOfDay2 = ""

switch hourOfDay {
case 0...5:
  timeOfDay2 = "Early morning"
case 6...11:
  timeOfDay2 = "Morning"
case 12...16:
  timeOfDay2 = "Afternoon"
case 17...19:
  timeOfDay2 = "Evening"
case 20..<24:
  timeOfDay2 = "Late evening"
default:
  timeOfDay2 = "INVALID HOUR!"
}

print(timeOfDay2)

It’s also possible to match a case to a condition based on a property of the value. As you learned in the first part of this tutorial series, you can use the modulo operator to determine if an integer is even or odd. Consider this code:

switch number {
case let x where x % 2 == 0:
  print("Even")
default:
  print("Odd")
}

This will print the following:

Even

This switch statement uses the let-where syntax, meaning the case will match only when a certain condition is true. The let part binds a value to a name, while the where part provides a Boolean condition that must be true for the case to match. In this example, you’ve designed the case to match if the value is even — that is, if the value modulo 2 equals 0.

The method by which you can match values based on conditions is known as pattern matching.

In the previous example, the binding introduced an unnecessary constant x; it’s simply another name for number. You are allowed to use number in the where clause and replace the binding with an underscore to ignore it:

switch number {
case _ where number % 2 == 0:
  print("Even")
default:
  print("Odd")
}

Partial Matching

Another way you can use switch statements with matching to great effect is as follows:

let coordinates = (x: 3, y: 2, z: 5)

switch coordinates {
case (0, 0, 0): // 1
  print("Origin")
case (_, 0, 0): // 2
  print("On the x-axis.")
case (0, _, 0): // 3
  print("On the y-axis.")
case (0, 0, _): // 4
  print("On the z-axis.")
default:        // 5
  print("Somewhere in space")
}

This switch statement makes use of partial matching. Here’s what each case does, in order:

  1. Matches precisely the case in which the value is (0, 0, 0). This is the origin of 3D space.
  2. Matches y=0, z=0 and any value of x. This means the coordinate is on the x-axis.
  3. Matches x=0, z=0 and any value of y. This means the coordinate is on the y-axis.
  4. Matches x=0, y=0 and any value of z. This means the coordinate is on the z-axis.
  5. Matches the remainder of coordinates.

You’re using the underscore to mean that you don’t care about the value. If you don’t want to ignore the value, then you can bind it and use it in your switch statement, like this:

switch coordinates {
case (0, 0, 0):
  print("Origin")
case (let x, 0, 0):
  print("On the x-axis at x = \(x)")
case (0, let y, 0):
  print("On the y-axis at y = \(y)")
case (0, 0, let z):
  print("On the z-axis at z = \(z)")
case let (x, y, z):
  print("Somewhere in space at x = \(x), y = \(y), z = \(z)")
}

Here, the axis cases use the let syntax to pull out the pertinent values. The code then prints the values using string interpolation to build the string.

Notice how you don’t need a default in this switch statement. This is because the final case is essentially the default; it matches anything, because there are no constraints on any part of the tuple. If the switch statement exhausts all possible values with its cases, then no default is necessary.

Also notice how you could use a single let to bind all values of the tuple: let (x, y, z) is the same as (let x, let y, let z).

Finally, you can use the same let-where syntax that you saw earlier to match more complex cases. For example:

switch coordinates {
case let (x, y, _) where y == x:
  print("Along the y = x line.")
case let (x, y, _) where y == x * x:
  print("Along the y = x^2 line.")
default:
  break
}

Here, you match the “y equals x” and “y equals x squared” lines.