Swift Enums — Powerful Enumerations with Associated Values
In this tutorial, you will learn about Swift Enums. We cover key concepts, practical examples, and best practices to help you master this topic.
Swift enums are first-class types that define a common group of related values and enable you to work with those values in a type-safe way, far beyond simple integer constants found in other languages.
What You'll Learn
- Basic enum syntax and pattern matching with switch
- Raw values and associated values
- Recursive enums
- Enum methods, computed properties, and protocol conformance
- Memory and performance characteristics
- Real-world enum patterns
Why It Matters
Enums in Swift are dramatically more powerful than in most languages. They can have methods, computed properties, initializers, conform to protocols, and carry associated data. This makes them ideal for modeling state machines, network responses, payment methods, navigation routes, and any set of mutually exclusive options.
Real-World Use
A ride-sharing app uses an enum to model the ride state: .requested, .driverAssigned(driverID: String), .inTransit(currentLocation: CLLocation), .completed, .cancelled(reason: String). Each state carries different associated data, and the compiler ensures all states are handled in every switch statement.
Learning Path
flowchart LR A[Generics
Lesson 15] --> B[Enums
You are here] B --> C[Views and UI
Lesson 17] B --> D[Table and Collection Views
Lesson 18] style B fill:#f90,color:#fff
Basic Enum Syntax
Enums declare a type with a fixed set of possible values using the case keyword.
enum CompassDirection {
case north
case south
case east
case west
}
var direction = CompassDirection.north
direction = .east
print("Direction: \(direction)")
switch direction {
case .north:
print("Going north")
case .south:
print("Going south")
case .east:
print("Going east")
case .west:
print("Going west")
}
Output:
Direction: east
Going east
Once the type is known, Swift infers the enum type so you can use .east shorthand. The switch must be exhaustive — all cases must be covered.
Raw Values
Enums can have raw values of the same type assigned to each case. Raw values can be strings, characters, integers, or floating-point numbers.
enum Planet: Int {
case mercury = 1
case venus
case earth
case mars
case jupiter
case saturn
case uranus
case neptune
}
enum HTTPStatus: Int {
case ok = 200
case created = 201
case badRequest = 400
case unauthorized = 401
case notFound = 404
case serverError = 500
}
enum Direction: String {
case north = "N"
case south = "S"
case east = "E"
case west = "W"
}
let earth = Planet.earth
print("Earth is planet #\(earth.rawValue)")
let status = HTTPStatus.notFound
print("Status code: \(status.rawValue)")
print("Direction abbreviation: \(Direction.north.rawValue)")
if let planet = Planet(rawValue: 3) {
print("Planet #3 is \(planet)")
}
if let error = HTTPStatus(rawValue: 404) {
print("Error: \(error)")
}
Output:
Earth is planet #3
Status code: 404
Direction abbreviation: N
Planet #3 is earth
Error: notFound
Integer raw values auto-increment if you do not assign them explicitly. Failable initializers like Planet(rawValue:) return nil if no matching case exists.
Associated Values
Associated values let you attach arbitrary data of different types to each enum case, making enums much more expressive.
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
case aztec(code: String, version: Int)
}
var productBarcode = Barcode.upc(0, 12345, 67890, 3)
productBarcode = .qrCode("https://example.com")
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem)-\(manufacturer)-\(product)-\(check)")
case .qrCode(let url):
print("QR Code: \(url)")
case .aztec(let code, let version):
print("Aztec v\(version): \(code)")
}
Output: QR Code: https://example.com
Each case can have different associated value types. Barcode.upc has four integers while Barcode.qrCode has a single string. Pattern matching extracts the values in the switch.
Value Binding Shorthand
You can place let or var before the case name to bind all associated values.
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC: \(numberSystem)-\(manufacturer)-\(product)-\(check)")
case let .qrCode(url):
print("QR Code: \(url)")
case let .aztec(code, version):
print("Aztec v\(version): \(code)")
}
Enum Methods and Properties
Enums can have computed properties, methods, and even initializers.
enum TrafficLight {
case red
case yellow
case green
var description: String {
switch self {
case .red:
return "Stop"
case .yellow:
return "Caution"
case .green:
return "Go"
}
}
var duration: Double {
switch self {
case .red: return 30.0
case .yellow: return 5.0
case .green: return 25.0
}
}
mutating func next() {
switch self {
case .red:
self = .green
case .yellow:
self = .red
case .green:
self = .yellow
}
}
func shouldStop() -> Bool {
return self == .red || self == .yellow
}
}
var light = TrafficLight.red
print("\(light): \(light.description)")
light.next()
print("\(light): \(light.description)")
light.next()
print("\(light): \(light.description)")
print("Should stop? \(light.shouldStop())")
Output:
red: Stop
green: Go
yellow: Caution
Should stop? true
The next() method is marked mutating because it changes self. Computed properties use switch to return different values per case.
Enums with Associated Values and Methods
Methods can work with associated values using pattern matching.
enum NetworkResult {
case success(data: Data, statusCode: Int)
case failure(error: Error, statusCode: Int)
case loading(progress: Double)
var description: String {
switch self {
case .success(_, let statusCode):
return "Success with status \(statusCode)"
case .failure(let error, let statusCode):
return "Error \(statusCode): \(error.localizedDescription)"
case .loading(let progress):
return "Loading: \(Int(progress * 100))%"
}
}
var data: Data? {
if case .success(let data, _) = self {
return data
}
return nil
}
}
let result = NetworkResult.loading(progress: 0.75)
print(result.description)
let completed = NetworkResult.success(data: Data(), statusCode: 200)
print(completed.description)
print("Has data: \(completed.data != nil)")
Output:
Loading: 75%
Success with status 200
Has data: true
Recursive Enums
A recursive enum has a case that refers to one or more instances of the same enum. You mark such cases with indirect.
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
case subtraction(ArithmeticExpression, ArithmeticExpression)
case division(ArithmeticExpression, ArithmeticExpression)
}
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case .number(let value):
return value
case .addition(let left, let right):
return evaluate(left) + evaluate(right)
case .multiplication(let left, let right):
return evaluate(left) * evaluate(right)
case .subtraction(let left, let right):
return evaluate(left) - evaluate(right)
case .division(let left, let right):
return evaluate(left) / evaluate(right)
}
}
let expression = ArithmeticExpression.addition(
.number(5),
.multiplication(.number(3), .number(4))
)
print("Result: \(evaluate(expression))")
Output: Result: 17
The expression 5 + (3 * 4) is represented as a recursive enum tree. The indirect keyword tells Swift to store the enum case as a reference (heap-allocated) to support arbitrary nesting.
Protocol Conformance
Enums can conform to protocols, including standard library protocols like Codable, CaseIterable, Comparable, and CustomStringConvertible.
enum CardSuit: String, CaseIterable, Codable, CustomStringConvertible {
case hearts = "H"
case diamonds = "D"
case clubs = "C"
case spades = "S"
var description: String {
switch self {
case .hearts: return "Hearts"
case .diamonds: return "Diamonds"
case .clubs: return "Clubs"
case .spades: return "Spades"
}
}
var symbol: String {
switch self {
case .hearts: return "♥"
case .diamonds: return "♦"
case .clubs: return "♣"
case .spades: return "♠"
}
}
}
print("All suits:")
for suit in CardSuit.allCases {
print(" \(suit.symbol) \(suit)")
}
let jsonData = try JSONEncoder().encode(CardSuit.hearts)
let decoded = try JSONDecoder().decode(CardSuit.self, from: jsonData)
print("Decoded: \(decoded)")
Output:
All suits:
♥ Hearts
♦ Diamonds
♣ Clubs
♠ Spades
Decoded: Hearts
CaseIterable provides the allCases property automatically. Codable serializes enums with raw or associated values.
Real-World Enum Patterns
State Machine
enum DownloadState {
case idle
case downloading(progress: Double)
case completed(fileURL: URL)
case failed(error: Error)
var progressDescription: String {
switch self {
case .idle:
return "Waiting to start"
case .downloading(let progress):
return "Downloading: \(Int(progress * 100))%"
case .completed(let fileURL):
return "Completed: \(fileURL.lastPathComponent)"
case .failed(let error):
return "Failed: \(error.localizedDescription)"
}
}
}
let state = DownloadState.downloading(progress: 0.5)
print(state.progressDescription)
Output: Downloading: 50%
Optional Values
Swift's own Optional is an enum:
// Simplified version of Swift's Optional
// enum Optional<Wrapped> {
// case none
// case some(Wrapped)
// }
let name: String? = "Alice"
switch name {
case .none:
print("No name provided")
case .some(let value):
print("Name is \(value)")
}
Output: Name is Alice
Common Mistakes
Not handling all cases in switch: Swift enforces exhaustive switch statements. Use a
defaultcase only when you explicitly want to ignore some cases — otherwise handle each case individually for better safety.Using raw values when associated values would be better: Raw values fit simple constant mappings. For complex data per case, use associated values instead.
Forgetting indirect for recursive enums: Recursive enums cause a compile error without the
indirectkeyword on the enum or individual cases.Modifying self in enum methods without mutating: Enum methods that change
selfmust be markedmutating.Overusing enums for unrelated cases: Cases in an enum should represent mutually exclusive variants of the same concept. Grouping unrelated values in one enum is poor design.
Practice Questions
- What is the difference between raw values and associated values in Swift enums?
- Why must switch statements over enums be exhaustive?
- When would you use
indirectfor an enum case? - Can enums conform to protocols like
CodableandCaseIterable? - Challenge: Build a
NetworkRequestenum with associated values that models GET, POST, PUT, DELETE methods. Each case should carry a URL, headers dictionary, and optional body data. Add a method that converts it to aURLRequest.
Mini Project
Create an OrderStatus enum for a food delivery app with:
- Cases:
.placed,.confirmed(estimatedMinutes: Int),.preparing(chefName: String),.outForDelivery(driverName: String, phone: String),.delivered,.cancelled(reason: String) - Computed property
displayText: Stringfor user-facing status - Computed property
isActive: Bool(true for in-progress states) - Method
timeRemaining: String?that returns estimated time if available - Conformance to
Codableso it can be sent over the network
FAQ
What's Next
Apply your enum knowledge to build real interfaces in Views and UI, or explore Table and Collection Views for data-driven list interfaces.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro