Enums & switch
Many values in real life come from a fixed list of choices: a traffic light is red, yellow or green; a payment is cash, UPI or card; an order is placed, packed, shipped or delivered. Storing these as strings like "shiped" invites typing mistakes that the compiler cannot catch.
Swift's enumerations (enums) define such a list as a type of its own. Combined with switch, which the compiler checks for completeness, enums are one of Swift's most loved features. In this lesson you will define enums, give them raw values and attached data, and match them with switch patterns.
Defining an enum and switching over it
An enum lists its cases. Once Swift knows a variable's type, you can write just .green instead of TrafficLight.green.
enum TrafficLight {
case red, yellow, green
}
var light = TrafficLight.red
light = .green
switch light {
case .red:
print("Stop")
case .yellow:
print("Get ready")
case .green:
print("Go")
}
print(light)Go green
The switch has a case for every value, so it needs no default. This is the big benefit: if you later add case flashing to the enum, the compiler shows an error at every switch that does not handle it yet. A default would hide that, so avoid default when switching over your own enums.
Raw values and CaseIterable
An enum can have a raw value of type Int, String or another simple type. Integer raw values count up automatically; string raw values default to the case name. Add CaseIterable to get an allCases array.
enum Weekday: Int, CaseIterable {
case monday = 1, tuesday, wednesday, thursday, friday, saturday, sunday
}
print(Weekday.wednesday.rawValue)
print(Weekday(rawValue: 6) ?? .monday)
print(Weekday(rawValue: 9) == nil)
print(Weekday.allCases.count)
enum Meal: String, CaseIterable {
case breakfast, lunch
case dinner = "Dinner (after 7 pm)"
}
for meal in Meal.allCases {
print(meal.rawValue)
}3 saturday true 7 breakfast lunch Dinner (after 7 pm)
Creating an enum from a raw value with Weekday(rawValue:) returns an optional, because a number like 9 matches no case. Raw values are handy when saving to a file or reading JSON, and allCases is perfect for filling a picker in SwiftUI.
Associated values
Cases can carry extra data called associated values, and each case can carry different data. A switch pulls the data out with let.
enum Payment {
case cash
case upi(id: String)
case card(lastFour: String, emi: Int)
}
func describe(_ payment: Payment) -> String {
switch payment {
case .cash:
return "Paid in cash"
case .upi(let id):
return "UPI from \(id)"
case .card(let lastFour, let emi) where emi > 0:
return "Card ending \(lastFour), \(emi) month EMI"
case .card(let lastFour, _):
return "Card ending \(lastFour)"
}
}
let payments: [Payment] = [.cash, .upi(id: "kabir@upi"),
.card(lastFour: "4821", emi: 6), .card(lastFour: "9910", emi: 0)]
for payment in payments {
print(describe(payment))
}
print(Payment.upi(id: "isha@upi"))Paid in cash UPI from kabir@upi Card ending 4821, 6 month EMI Card ending 9910 upi(id: "isha@upi")
A where clause adds an extra condition to a case, and _ ignores a value you do not need. Cases are checked from top to bottom, so the EMI case must come before the general card case. Associated values let one type describe very different situations safely: a UPI payment simply cannot have a missing ID.
Powerful switch patterns
switch works with any type, not only enums. Cases can match several values, ranges, tuples and conditions, and can bind parts of the value to names.
func classify(score: Int, wickets: Int) -> String {
switch (score, wickets) {
case (100..., _):
return "Century!"
case (50..<100, 0...2):
return "Strong start"
case (_, 8...10):
return "Collapse"
case let (runs, out) where runs > out * 20:
return "Steady"
default:
return "Struggling"
}
}
print(classify(score: 120, wickets: 9))
print(classify(score: 64, wickets: 1))
print(classify(score: 40, wickets: 9))
print(classify(score: 90, wickets: 3))
print(classify(score: 30, wickets: 4))Century! Strong start Collapse Steady Struggling
Trace the fourth call: 90 is in 50..<100 but 3 wickets is not in 0...2, and 3 is not in 8...10, so Swift reaches the where case: 90 is more than 3 times 20, so the result is "Steady". Integers have endless possible values, so this switch needs a default. You can also list several values in one case, for example case "A", "B":.
Methods, computed properties and if case
Enums can have computed properties and methods, just like structs. A method that changes self must be marked mutating. To test for just one case without a full switch, use if case.
enum OrderStatus {
case placed, packed, shipped(trackingID: String), delivered
var isActive: Bool {
switch self {
case .delivered: false
default: true
}
}
mutating func advance() {
switch self {
case .placed: self = .packed
case .packed: self = .shipped(trackingID: "IN2026X")
case .shipped: self = .delivered
case .delivered: break
}
}
}
var status = OrderStatus.placed
for _ in 1...4 {
status.advance()
print(status, status.isActive)
}
if case .delivered = status {
print("Ask for a review")
}packed true shipped(trackingID: "IN2026X") true delivered false delivered false Ask for a review
The fourth advance() does nothing because a delivered order stays delivered; break in a switch case means "do nothing here". isActive uses a switch expression with a default, which is fine for a property that only cares about one case.
Common mistakes
- Using strings for fixed choices.
"shipped"versus"Shipped"bugs disappear with an enum. - Adding
defaultto your own enum switches. You lose the compiler's reminder when new cases are added. - Forgetting that
init(rawValue:)is optional. Unwrap it or use??. - Putting a general case before a specific one. The first matching case wins; order cases from most to least specific.
Key points
- An enum defines a type with a fixed set of cases;
.caseNameworks when the type is known. switchmust be exhaustive and never falls through; avoiddefaultfor your own enums.- Raw values (
Int,String) andCaseIterableconnect enums to stored data and lists. - Associated values attach different data to each case;
case .upi(let id)extracts it. switchpatterns include ranges, tuples,letbindings,_andwhereclauses.- Enums can have computed properties and
mutatingmethods;if casetests a single case.
Exercise
Create an enum Ticket for a cinema in Pune with cases regular, premium and recliner(withFood: Bool). Add a computed property price returning 180, 320, and 550 for the recliner (or 750 with food). Print the price of each ticket in the array [.regular, .recliner(withFood: true), .premium, .recliner(withFood: false)] and the total.
Show solution
enum Ticket {
case regular, premium
case recliner(withFood: Bool)
var price: Int {
switch self {
case .regular: 180
case .premium: 320
case .recliner(let withFood): withFood ? 750 : 550
}
}
}
let booking: [Ticket] = [.regular, .recliner(withFood: true), .premium, .recliner(withFood: false)]
var total = 0
for ticket in booking {
print(ticket.price)
total += ticket.price
}
print("Total: \(total)")180 750 320 550 Total: 1800
The switch covers every case, so it needs no default. The recliner case extracts withFood and uses a ternary to choose between the two prices.
Quick Quiz
1. Why should you avoid default when switching over your own enum?
Without default, adding a case makes every incomplete switch a compile error.
2. What does Weekday(rawValue: 9) return if no case has raw value 9?
Initialising from a raw value returns an optional.
3. How do you read the ID from case upi(id: String) in a switch?
A let pattern inside the case binds the associated value to a name.
4. Which keyword must a method that changes self in an enum have?
Enums and structs are value types, so methods that modify them are marked mutating.
Learn Swift with a mentor
Free lessons get you started. Our Mobile App Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
