Back to blog

Switch Statements in Swift Explained


Aasif Khan
By Aasif Khan | Last Updated on January 15th, 2024 8:33 am | 4-min read

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. Using the switch statement often results in more concise code that’s easier to read.

In this app development tutorial, 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

Describe your app idea
and AI will build your App

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.

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. This makes the switch exhausive.

  • 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:

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("Not in visible spectrum") }
See what happens here? For every range of wavelengths we define an interval with the half-open range operator a..

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 or type. 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 tuple’s 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 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.
    We’re evaluating the value of httpResponse with a switch statement, responding to different values of the Response enum.
  2. In the above code we’re defining two cases that are 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.

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!


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts