Skip to content

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.

swift
// 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:

swift
var direction = CompassDirection.north
direction = .south // Type is inferred as CompassDirection

Associated 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.

swift
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).

swift
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.

swift
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.

swift
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.

swift
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.

swift
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.

swift
enum ViewMode {
    case readOnly
    case editing
}

var currentMode = ViewMode.readOnly
// If user taps edit, switch to .editing and update the UI

Enums with Methods

Enums are more than just a list of names; they can have methods and computed properties to encapsulate behavior.

swift
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 */ }