Overview
One of the strengths of using switch in Swift is pattern matching. When the value you are switching over is an enum, Swift can check whether your switch handles every possible case.
That compiler check is called exhaustiveness. It is one reason enums and switch statements work so well together: you describe the possible values in one place, then Swift helps you remember to handle them in your logic.
Why Enums Work So Well with Switch
An enum defines a fixed set of possible values. For example, here is an enum for the planets in our solar system:
enum Planet {
case mercury
case venus
case earth
case mars
case jupiter
case saturn
case uranus
case neptune
}Because Swift knows every possible Planet, it can check whether a switch statement covers them all.
An Incomplete Enum Switch
Here is a switch that only handles three planets:
let planet: Planet = .earth
switch planet {
case .mercury:
print("The planet closest to the sun")
case .earth:
print("This is where we live")
case .neptune:
print("The planet farthest from the sun")
}This will not compile. Swift will show an error like:
Switch must be exhaustiveThat message means the switch does not handle every possible value of Planet. In this case, venus, mars, jupiter, saturn, and uranus are missing.
Option 1: Add the Missing Cases
The safest fix is to add a case for every remaining enum value:
switch planet {
case .mercury:
print("The planet closest to the sun")
case .venus:
print("The second planet from the sun")
case .earth:
print("This is where we live")
case .mars:
print("The red planet")
case .jupiter:
print("The largest planet")
case .saturn:
print("The planet with famous rings")
case .uranus:
print("An ice giant")
case .neptune:
print("The planet farthest from the sun")
}Xcode can help here. When the compiler complains that a switch must be exhaustive, its fix-it can add missing cases for you. You still need to fill in the behavior for each case, but the compiler gives you the checklist.
Option 2: Use Default
You can also add a default case:
switch planet {
case .mercury:
print("The planet closest to the sun")
case .earth:
print("This is where we live")
case .neptune:
print("The planet farthest from the sun")
default:
print("A planet inside the solar system")
}This compiles because default catches everything that was not matched above.
default when all remaining cases truly share the same behavior.Why Explicit Cases Are Usually Safer
A default case can be convenient, but it also hides future changes. Imagine you add a new enum case later:
enum Planet {
case mercury, venus, earth, mars
case jupiter, saturn, uranus, neptune
case pluto
}If your switch handles every case explicitly, Swift will tell you which switches need updating. If your switch uses default, the new case quietly falls into the default behavior.
That is not always wrong, but it is something to choose intentionally.
A Practical App Example
Enums are especially useful for app states because a screen is usually in one clear state at a time.
enum LoadingState {
case idle
case loading
case loaded([String])
case failed(String)
}
let state = LoadingState.loaded(["Mercury", "Venus", "Earth"])
switch state {
case .idle:
print("Ready to load")
case .loading:
print("Loading...")
case .loaded(let planets):
print("Loaded \\(planets.count) planets")
case .failed(let message):
print("Error: \\(message)")
}This switch is exhaustive and it also extracts associated values with let. The loaded case gives you the array, and the failed case gives you the error message.
Common Mistakes
Using default too early
If you are still designing the enum, avoid adding default just to silence the compiler. Let the compiler show you what is missing.
Forgetting that enum cases can carry values
Enum cases can store associated values. When they do, use pattern matching to extract the values inside the switch.
Writing the same code in every case
If many cases do the same thing, combine them with commas or use a carefully chosen default.
Quick Reference
| Pattern | What It Does |
|---|---|
case .earth: | Matches one specific enum case |
case .venus, .mars: | Matches either of the listed enum cases |
default: | Matches anything that was not handled earlier |
case .loaded(let items): | Matches a case with an associated value and stores it in a local constant |
case let .failed(message): | Another common syntax for extracting an associated value |
Summary
- Enums define a fixed set of possible values.
- Switch statements are a strong fit for enums because Swift can check exhaustiveness.
- If a switch over an enum misses cases, Swift shows the
Switch must be exhaustivecompiler error. - Adding every missing case is usually safer than relying on
default. - Use
defaultwhen all remaining cases truly share the same behavior. - Switch statements can extract associated values from enum cases.
That is the core idea: enums describe the possible states, and switch statements describe what should happen for each state. When you use them together, Swift can help keep your code honest as your app grows.