Enums
An Enum (short for Enumeration) defines a common type for a group of related values. They allow you to work with these values in a type-safe way, preventing errors from "magic strings" or numbers.
Basic Syntax
You can define possible options using the case keyword. Cases can be on separate lines or on a single line separated by commas. Both are identical in functionality.
// Separate lines
enum CompassDirection {
case north
case south
case east
case west
}
// Single line (more compact)
enum CompassDirection {
case north, south, east, west
}Once defined, you can use "dot syntax" as a shortcut to change the value:
var direction = CompassDirection.north
direction = .south // Type is inferred as CompassDirectionAssociated Values
Enums can store additional custom information alongside their cases. This is incredibly useful when a specific case needs extra data to be meaningful.
You can provide names (labels) for these associated values to make your code easier to read, or leave them unlabeled if the context is obvious.
enum Barcode {
// Unlabeled associated values (relies purely on order)
case upc(Int, Int, Int, Int)
// Labeled associated value (clearer intent)
case qrCode(link: String)
}
let productCode = Barcode.upc(8, 85909, 51226, 3)
let websiteCode = Barcode.qrCode(link: "piratedev.com")Extracting Associated Values
To read the data stored inside an associated value, you use pattern matching. While switch statements are common, if case let and guard case let are perfect when you only care about extracting data from one specific case and want to completely ignore the rest.
Swift offers two interchangeable styles for doing this. You can place the let before the case (which automatically binds all values), or inside the parentheses (which feels similar to unwrapping a tuple).
let currentCode = Barcode.qrCode(link: "swift.org")
// Style 1: Placing 'let' outside binds all associated values at once
if case let .qrCode(link) = currentCode {
print("Scanning QR Code leading to: \(link)")
}
// Style 2: Placing 'let' inside binds specific values individually
// This is often preferred when unwrapping single values
if case .qrCode(let link) = currentCode {
print("Scanning QR Code leading to: \(link)")
}
// Using guard case (typically used inside a function to exit early)
func process(code: Barcode) {
// You can use either style here as well
guard case .qrCode(let link) = code else {
print("Not a QR code.")
return
}
print("Processing website: \(link)")
}Raw Values
Raw values are prepopulated default values that back each case. Unlike associated values, raw values must be of the same type for all cases, and each value must be unique.
Supported Types
Raw values can be Strings, Characters, or any Integer or Floating-point number type.
Implicit vs Explicit
You do not always have to type out every raw value.
For Integers, if you don't provide a value for the first case, it starts at 0. If you set the first case to 1, subsequent cases will auto-increment (2, 3, 4...).
For Strings, if you don't provide a value, the case name itself becomes the raw value.
enum Planet: Int {
case mercury = 1, venus, earth, mars // earth is 3
}
enum State: String {
case active, inactive // rawValue of .active is "active"
}Iterating over Enums (CaseIterable)
If you need to loop through all the possible cases of an enum or count how many cases there are, you can make your enum adopt the CaseIterable built-in protocol.
Swift will automatically generate an allCases array for you.
enum Beverage: CaseIterable {
case coffee, tea, juice, water
}
let numberOfChoices = Beverage.allCases.count
print("We have \(numberOfChoices) beverages available.")
for beverage in Beverage.allCases {
print(beverage)
}Modeling State with Enums
Enums are the perfect tool for modeling state in Swift because their cases are mutually exclusive. A system can only be in exactly one state at any given time.
For example, a network request cannot be both loading and successful simultaneously. Using an enum guarantees this rule at the compiler level, making your code incredibly safe and completely eliminating the chance of having conflicting data.
Common State Modeling Patterns
There are a few standard patterns you will see constantly in Swift development when handling state.
The Basic State Machine
This pattern uses simple cases to represent the current phase of a process. It is perfect for tracking what a screen or component is doing right now.
enum ScreenState {
case loading
case empty
case loaded
case error
}Data-Driven State (Associated Values)
This is one of the most powerful patterns in Swift. Instead of storing your downloaded data in a separate variable that might accidentally get out of sync with your state, you attach the data directly to the successful state using associated values. You can do the exact same thing with errors.
enum NetworkState {
case idle
case fetching
case success(data: String)
case failed(error: Error)
}
// The data literally only exists if the state is successful
let currentState = NetworkState.success(data: "User Profile Loaded")UI Mode Switching
Enums are also fantastic for tracking the current mode of a user interface. This is frequently used to determine what buttons or layouts to show the user.
enum ViewMode {
case readOnly
case editing
}
var currentMode = ViewMode.readOnly
// If user taps edit, switch to .editing and update the UIEnums with Methods
Enums are more than just a list of names; they can have methods and computed properties to encapsulate behavior.
enum MessageStatus {
case sent, delivered, read, failed
// Computed property for UI logic
var iconName: String {
switch self {
case .sent: "paperplane"
case .delivered: "checkmark"
case .read: "checkmark.seal"
case .failed: "exclamationmark.circle"
}
}
func canRetry() -> Bool {
self == .failed
}
}
let status = MessageStatus.failed
print(status.iconName) // "exclamationmark.circle"
if status.canRetry() { /* Show retry button */ }