Switch Statements In Swift Explained

Written by Reinder de Vries on October 11 2018 in App Development

Switch Statements In Swift Explained

The switch statement in Swift lets you inspect a value and match it with a number of cases. It’s particularly effective for taking concise decisions based on one variable that can contain a number of possible values. And using the switch statement often results in more concise code that’s easier to read.

In this article, you’ll learn how to use the switch statement in Swift. We’ll get into:

  • What the switch statement is, and what it’s for
  • How to set up the different cases for switch
  • How to use defaults, compound cases and fallthrough
  • How to match switch with interval and range cases
  • How to work with tuples in a switch statement
  • Using where in the switch statement

And why, when and how all that’s useful in practical iOS development! Ready? Let’s go.

  1. What’s A Switch Statement?
  2. Default, Compound Cases And Fallthrough
  3. Interval Matching With Switch Statements
  4. Tuples And Value Bindings
  5. Switch Statement Pattern Matching With Where
  6. Further Reading

What’s A Switch Statement?

Here’s what the Swift Language Guide has to say about the switch statement:

A switch statement considers a value and compares it against several possible matching patterns.

Let’s find out what that means, with an example. Consider the following enumeration:

enum Compass {
    case north
    case east
    case south
    case west
}

When you want to find your bearings, you could use this code:

let heading = Compass.south

if heading == .north {
    print("You're heading north!")
} else if heading == .east {
    print("You're heading east!")
} else if heading == .south {
    print("You're heading south!")
} else if heading == .west {
    print("You're heading west!")
}

The above code uses an if statement to evaluate the value of heading, and print out the relevant line of text.

Here’s how you can do the same using a switch statement:

switch heading {
case .north:
    print("You're heading north!")
case .east:
    print("You're heading east!")
case .south:
    print("You're heading south!")
case .west:
    print("You're heading west!")
}

The syntax of the switch statement is simple:

  • First, the switch keyword, and then an expression, such as the constant heading. This is the value that’s being considered by the switch block.
  • Then, a number of cases with case. In the above example we’re considering every possible value of the Compass enumeration.

The switch statement is much more powerful than it seems, especially compared to the if statement. One of its advantages is that every switch block needs to be exhaustive.

Here, check out what happens when we forget to include .east:

switch heading {
case .north:
    print("You're heading north!")
case .south:
    print("You're heading south!")
case .west:
    print("You're heading west!")
}

The output is: error: switch must be exhaustive.

Oops! That means that we’ll need to assess every value of Compass. When we don’t, we’ll see a compile-time error. This is particularly helpful if you’re changing an enumeration later on in your app. You wouldn’t get a warning if you had used if statements.

The advantages of the switch statement go beyond merely checking enums for exhaustiveness (Scrabble word!). Its cases are checked at compile-time, so you can spot numerous errors before your app runs.

Learn how to build iOS apps

Get started with iOS 12 and Swift 5

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 5 and Xcode 10.

Default, Compound Cases And Fallthrough

Even though a switch statement needs to be exhaustive, you don’t have to explicitly specify every option.

Here, consider the following Authorization enumeration:

enum Authorization {
    case granted
    case undetermined
    case unauthorized
    case denied
    case restricted
}

When you want to give a user access to a resource based on their authorization state, you could do this:

switch state {
case .granted:
    print("Access granted. You may proceed.")
default:
    print("YOU SHALL NOT PASS!!")
}

The above code uses the default case to respond to any case that’s not handled.

  • When state equals .granted, the first case is executed.
  • When state has any other value than .granted, the default case is executed.

And you can also use compound cases, like this:

switch state {
case .granted:
    print("Access granted. You may proceed.")
case .undetermined:
    print("Please provide your clearance code.")
case .unauthorized, .denied, .restricted:
    print("Access denied!")
}

The last case combines several cases on one line. Everyone of those .unauthorized, .denied and .restricted cases will trigger the “Access denied!” response.

And last but not least, let’s take a look at implicit fallthrough. In most programming languages, switch statement cases implicitly “fall through” to the next case. Execution starts with the first matching case, and continues down until you explicitly stop execution with break.

In Swift, it’s exactly the opposite. Every switch case will automatically halt. You don’t need to use break, because Swift switch statements don’t implicitly fall through.

This is a deliberate design decision. In most cases, you don’t want your case to fall through. When you do, you can use fallthrough explicitly. Like this:

var message = "Response: "
let state = Authorization.undetermined

switch state {
case .granted:
    message += "Access granted. You may proceed."
case .undetermined:
    message += "Please provide your clearance code. "
    fallthrough
default:
    message += "Access denied!"
}

print(message)

In the above example, the .undetermined case falls through to the default case. So, when state is .undetermined, both cases are executed, and two strings are added to message, which results in this output:

Output: Response: Please provide your clearance code. Access denied!

And that’s because of the fallthrough keyword in the .undetermined case block.

You don’t have to explicitly break a case block, but if you want to prematurely exit a particular case you can use break. Just as with for and while loops.

Interval Matching With Switch Statements

You can use the switch statements to match intervals and ranges. Consider, for instance, how humans perceive visible light with different wavelengths:

Image by Inductiveload, NASA – CC BY-SA 3.0

We can check if a particular wavelength produces the color violet or orange. Like this:

let wavelength = 398

if wavelength >= 380 && wavelength < 450 {
    print("It's violet!")
} else if wavelength >= 590 && wavelength < 620 {
    print("It's orange!")
}

You can imagine that if we use an if statement to check the wavelengths of violet, blue, green, yellow, orange and red colors, the conditional becomes quite messy.

Instead, we do this with the switch statement:

let wavelength = 620

switch wavelength {
case 380..<450:
    print("Purple!")
case 450..<495:
    print("Blue!")
case 495..<570:
    print("Green!")
case 570..<590:
    print("Yellow!")
case 590..<620:
    print("Orange!")
case 620..<750:
    print("Red!")
default:
    print("Invisible spectrum")
}

See what happens here? For every range of wavelengths we define an interval with the half-open range operator a..<b. For instance, a range of 380..<450 starts at 380 nanometer and ends at 450 nm (not including 450).

In the above code, the value of wavelength is evaluated against the cases in the switch statement. The code determines that 620 falls within the range of 620..<750 and prints out Red! accordingly.

Tuples And Value Bindings

This is where it gets interesting! You can use the switch statement in Swift with tuples.

A tuple is a simple ordered list of two or more values. It’s written between parentheses, like this:

let flight = (747, "SFO")

The type of flight is (Int, String). Once a tuple is defined, you can’t change its order. Tuples are ideal for passing several associated values in one variable.

You can access the values of a tuple with an index number, like this:

print(flight.1)
// Output: SFO

It’s more convenient to name the tuples values. Like this:

let flight = (airplane: 747, airport: "SFO")
print(flgith.airplane)
// Output: 747

You can use tuples together with switch statements. Like this:

for i in 1...100
{
    switch (i % 3 == 0, i % 5 == 0)
    {
    case (true, false):
        print("Fizz")
    case (false, true):
        print("Buzz")
    case (true, true):
        print("FizzBuzz")
    default:
        print(i)
    }
}

The above code is part of a coding challenge called FizzBuzz.

In the above code we’re creating a tuple from two values, the result of i % 3 == 0 and i % 5 == 0. These are boolean operations, so the type of the tuple is (true, true).

We can now respond to different tuple values, such as (true, false) and (true, true). As such, this happens in the code:

  • When i is divisible by 3, print “Fizz”
  • When i is divisible by 5, print “Buzz”
  • When i is divisible by both 3 and 5, print “FizzBuzz”
  • If neither of those cases matches, just print the value of i

Because we’re using switch to evaluate a tuple, we can react to different matching values in the tuple.

Here, check this out:

let airplane = (type: "737-800", heading: "LAX")

switch airplane {
case ("737-800", _):
    print("This airplane is a 737-800 model.")
case (let type, "LAX"):
    print("This \(type) is headed for Los Angeles Intl. Airport")
default:
    print("Unknown airplane and heading.")
}

In the above code we’re responding to 3 different cases:

  1. When the airplane type is a 737-800 the code prints This airplane is a 737-800 model. See how the underscore _ matches any value for airplane.heading?
  2. When the airplane heading is LAX, irregardless of airplane type, the code prints out This … is headed for Los Angeles Intl. Airport.
  3. Anything else, the code prints Unknown airplane and heading.

Consider what happens in the above scenario, where the value of airplane is (type: "737-800", heading: "LAX"). Which of the 3 cases is executed?

It’s the first case, because that’s the first case that matches. The type of the airplane is 373-800. Even though the LAX heading could potentially match the second case, the ("737-800", _) is matched earlier. Don’t forget that your code executes line by line, from top to bottom!

Then, consider what happens when the value of airplane is (type: "747", heading: "LAX"). Now the second case is executed, because the heading is LAX.

The let type in case (let type, "LAX"): is called a value binding. This temporarily binds the value from the tuple to a constant type. Within the switch case you can now use that constant, such as printing it out. Neat!

Switch Statement Pattern Matching With Where

OK, there’s one last way of using the switch statement that you have to get to know. It’s by using where.

Here, check out the following Swift code:

enum Response {
    case error(Int, String)
    case success
}

let httpResponse = Response.success

switch httpResponse {
case .error(let code, let status) where code > 399:
    print("HTTP Error: \(code) \(status)")
case .error:
    print("HTTP Request failed")
case .success:
    print("HTTP Request was successful")
}

// Output: HTTP Request was successful

In the above code, two things happen:

  1. We’re defining an enumeration called Response, that has two cases: .error and .success. The .error case has two associated values, an integer and a string.
  2. We’re evaluating the value of httpResponse with a switch statement, responding to different values of the Response enum.

In the above code we’re defining two cases that are very clear, namely .error and .success. Imagine a value of Response comes back and we simply want to check if the response is OK or not. For that, we use .error and .success.

Consider what happens when you change the value of httpResponse to:

let httpResponse = Response.error(404, "Not Found")
// Output: HTTP Error: 404 Not Found

And to:

let httpResponse = Response.error(301, "Moved Permanently")
// Output: HTTP Request failed

What happens there?

There’s one .error case that we’re particularly interested in, namely an error code that’s greater than 399.

Here’s the relevant code:

case .error(let code, let status) where code > 399:
    print("HTTP Error: \(code) \(status)")

The official HTTP Status Codes, which are used around the internet, indicate that status codes from 400 and up are client and server errors. Differently said, when you get one of those, something went wrong.

In the above code, we’re binding the code and status constants to the associated values of the Response enumeration. We’re also using the where keyword to indicate that this case should execute for any value of code that’s greater than 399.

You can almost literally read that as “cases of error where code is greater than 399”. It’s quite intuitive, and clearly defines that we’re specially interested in those error codes.

Learn how to build iOS apps

Get started with iOS 12 and Swift 5

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 5 and Xcode 10.

Further Reading

The if statement isn’t the only way to make decisions in your code. The switch statement provides numerous ways to consider a value, and to compare it with matching patterns.

And it’s especially powerful when combined with tuples, pattern matching, ranges, value bindings and where!

Want to learn more? Check out these resources:

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.