Swift Enums

Model a fixed set of options with enums, attach raw values and associated data, add computed properties and methods, loop over cases, and handle every case with switch.

Lesson 14 of 43Intermediate18 min read

Many values in an app come from a fixed list of options: a compass direction, a T-shirt size, the state of a download, a payment method. Storing them as strings like "large" invites typos and gives the compiler nothing to check. An enum (enumeration) defines a type whose only possible values are the cases you list.

Swift's enums are unusually powerful. Cases can have raw values, such as a string for saving to a file. They can carry associated values: different data for each case, like a download that's in progress with a percentage, or failed with an error message. Enums can also have properties and methods, just like structs.

Combined with switch, enums make code both safer and clearer: the compiler checks that every case is handled, and adding a new case later points you to every place that needs updating. In this lesson you'll use all of these features with realistic examples.

Declaring and using enums

Swift
enum Direction {
    case north, south, east, west
}

var heading = Direction.north
heading = .west              // the type is known, so .west is enough

switch heading {
case .north: print("Up")
case .south: print("Down")
case .east, .west: print("Sideways")
}
  • Enum names use UpperCamelCase; cases use lowerCamelCase.
  • Once Swift knows the type, write cases with a leading dot: .west.
  • A switch that covers every case needs no default, and the compiler warns if a case is missing.

Raw values and CaseIterable

Give every case a fixed raw value of type String, Int or another simple type:

Swift
enum Size: String, CaseIterable {
    case small = "S", medium = "M", large = "L"
}

Size.large.rawValue          // "L"
Size(rawValue: "M")          // Optional(Size.medium)
Size.allCases.count          // 3
  • For String raw values you can leave the value out, and it defaults to the case name: case small has the raw value "small".
  • Int raw values count up from 0 automatically.
  • init(rawValue:) returns an optional, because the text might not match any case.
  • Adopting CaseIterable provides allCases, a list of every case, which is handy for menus and pickers.

Associated values

Each case can carry its own data, of different types:

Swift
enum DownloadState {
    case waiting
    case inProgress(percent: Int)
    case finished(fileURL: String)
    case failed(message: String)
}

let state = DownloadState.inProgress(percent: 40)
switch state {
case .waiting: print("Queued")
case .inProgress(let percent): print("\(percent)%")
case .finished(let url): print("Saved to \(url)")
case .failed(let message): print("Error: \(message)")
}

let inside the case pattern extracts the data. Associated values are the natural way to model states and results, where each outcome needs different information.

Properties and methods

Enums can have computed properties and methods, often implemented with switch self:

Swift
enum Planet {
    case mercury, earth, mars
    var gravity: Double {
        switch self {
        case .mercury: 3.7
        case .earth: 9.8
        case .mars: 3.7
        }
    }
}

A method that changes self to another case must be mutating. In fact, Swift's own Optional is an enum with two cases, .some(value) and .none, which is why switch and if case work on optionals too.

Comparing enum values

You can compare simple enum cases with == straight away, because Swift makes enums without associated values Equatable automatically:

Swift
if heading == .north { print("Heading up") }

Once cases carry associated values, Swift can't assume how to compare them, so == stops working until you ask for it. Add Equatable to the declaration and Swift writes the comparison for you, as long as every associated value is Equatable too:

Swift
enum Status: Equatable {
    case loading
    case failed(String)
}
print(Status.failed("Offline") == .failed("Offline"))   // true

To check only which case a value is, ignoring its data, use pattern matching instead: if case .failed = status { ... } works with or without Equatable.

One enum instead of several Booleans

A common beginner design tracks a screen's state with separate flags:

Swift
var isLoading = false
var hasError = false
var items: [String] = []

Nothing stops isLoading and hasError both being true, a combination that makes no sense. An enum makes impossible states impossible to write:

Swift
enum LoadState {
    case idle
    case loading
    case loaded([String])
    case failed(String)
}
var state = LoadState.idle

The value is always exactly one case, and the data travels with the case that needs it: items only exist once loading has succeeded, and an error message only exists after a failure. A switch over state then forces every screen to handle each situation.

Swift Enums Syntax

Enum syntax

enum Light { case red, amber, green }
enum Size: String, CaseIterable { case small = "S", large = "L" }
Size.small.rawValue   Size(rawValue: "L")   Size.allCases
enum Result { case success(Int), failure(String) }
switch r {
case .success(let n): print(n)
case .failure(let msg): print(msg)
}
if case .success(let n) = r { print(n) }

Swift Enums Summary

  • An enum defines a type with a fixed set of cases; write .case when the type is known.
  • A switch over every case needs no default and is checked by the compiler.
  • Raw values (String, Int) map cases to fixed values; init(rawValue:) returns an optional.
  • CaseIterable adds allCases; associated values let each case carry its own data.
  • Enums can have computed properties and methods; Optional itself is an enum.

Swift Enums Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Sizes with raw values

Swift
enum Size: String, CaseIterable {
    case small = "S"
    case medium = "M"
    case large = "L"

    var price: Double {
        switch self {
        case .small: 2.5
        case .medium: 3.0
        case .large: 3.5
        }
    }
}

for size in Size.allCases {
    print("\(size.rawValue): £\(size.price)")
}

let typed = "M"
if let chosen = Size(rawValue: typed) {
    print("You chose \(chosen)")
}
print(Size(rawValue: "XL") == nil)
Output
S: £2.5
M: £3.0
L: £3.5
You chose medium
true
Line-by-line explanation
  1. Line 1String raw values and CaseIterable together.
  2. Lines 6 to 12A computed property using switch self; each branch is a single value.
  3. Line 15allCases lists every size, in declaration order.
  4. Line 20init(rawValue:) returns an optional, so if let unwraps it. Printing an enum shows its case name.
Example 2

Associated values for download states

Swift
enum DownloadState {
    case waiting
    case inProgress(percent: Int)
    case finished(fileName: String)
    case failed(message: String, canRetry: Bool)
}

func label(for state: DownloadState) -> String {
    switch state {
    case .waiting:
        return "Waiting..."
    case .inProgress(let percent):
        return "Downloading \(percent)%"
    case .finished(let name):
        return "Saved \(name)"
    case .failed(let message, let canRetry):
        return message + (canRetry ? " (tap to retry)" : "")
    }
}

let states: [DownloadState] = [.waiting, .inProgress(percent: 45),
                               .finished(fileName: "photo.jpg"),
                               .failed(message: "No connection", canRetry: true)]
states.forEach { print(label(for: $0)) }
Output
Waiting...
Downloading 45%
Saved photo.jpg
No connection (tap to retry)
Line-by-line explanation
  1. Lines 3 to 5Each case carries different data.
  2. Line 9Every case is handled, so no default is needed.
  3. Lines 12 to 17let extracts the associated values inside each case.
Example 3

A mutating enum and if case

Swift
enum TrafficLight {
    case red, green, amber

    mutating func next() {
        switch self {
        case .red: self = .green
        case .green: self = .amber
        case .amber: self = .red
        }
    }
}

var light = TrafficLight.red
for _ in 1...4 {
    print(light, terminator: " ")
    light.next()
}
print()

let result: Result<Int, Error> = .success(42)
if case .success(let value) = result {
    print("Got \(value)")
}
Output
red green amber red 
Got 42
Line-by-line explanation
  1. Lines 4 to 10A mutating method replaces self with the next case.
  2. Lines 14 to 17The light cycles through its states.
  3. Line 20Swift's built-in Result is an enum; if case checks for one case without a full switch.

Common Swift Enums Mistakes

Strings instead of an enum

Don't
var size = "meduim"             // typo compiles fine
Do
var size = Size.medium          // a typo is a compile error

Forcing a raw value conversion

Don't
let size = Size(rawValue: input)!    // crashes for unexpected input
Do
let size = Size(rawValue: input) ?? .medium

default hiding new cases

Don't
switch state {
case .waiting: showSpinner()
default: break                   // a new case added later is silently ignored
}
Do
switch state {
case .waiting: showSpinner()
case .inProgress, .finished, .failed: break
}

Swift Enums in Real Projects

Enums in Swift apps

Apps model screen state with enums such as case loading, loaded([Item]), failed(Error), and SwiftUI views switch over them to show the right content. Raw-value enums define settings, sort orders and tabs, often saved with @AppStorage. Swift's own Optional and Result are enums, and network code commonly returns Result<Data, Error>.

Swift Enums FAQ

How do I convert a string to an enum in Swift?

Give the enum a String raw type, then use init(rawValue:): Size(rawValue: "M"). It returns an optional, which is nil when no case has that raw value, so unwrap it with if let or provide a default with ??.

How do I loop over all cases of an enum in Swift?

Add CaseIterable to the enum's declaration, then use the generated allCases array: for size in Size.allCases { print(size) }. It lists the cases in the order they're declared.

What is the difference between raw values and associated values?

A raw value is a fixed value chosen when you declare the case, the same every time, such as case small = "S". An associated value is data attached when you create a value, and it can differ each time, such as .inProgress(percent: 40). An enum with a raw type can't have associated values at all.

Can a Swift enum have methods and properties?

Yes. Enums can have methods, computed properties, initializers and static members, and they can conform to protocols. They can't have stored instance properties: put data in associated values instead.

Why do Swift enum cases start with a dot?

The dot is shorthand for the enum's name when Swift already knows the type. heading = .west means heading = Direction.west, because heading is a Direction. When the type isn't known, you write the full name.

Swift Enums Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the declaration so the enum gets an allCases list.

enum Weekday:  {
    case mon, tue, wed, thu, fri
}
Predict the outputeasy

What does this print?

enum Coin: Int { case penny = 1, nickel = 5, dime = 10 }
print(Coin.dime.rawValue)
Multiple choicemedium

What type does Size(rawValue: "XL") return?

Predict the outputmedium

What does this print?

enum Shape { case circle(r: Double), square(side: Double) }
let s = Shape.square(side: 3)
switch s {
case .circle(let r): print(3.14 * r * r)
case .square(let side): print(side * side)
}
Multiple choicehard

Why avoid default when switching over your own enum?

Swift Enums Quiz

5 questions to check what you learned.

  1. How do you write a case when Swift already knows the enum type?

    • .west
    • Direction::west
    • west()
    • "west"
  2. What are associated values?

    • Raw values
    • Static properties
    • Data attached to a specific case, which can differ for each case
    • Default values
  3. What's the default raw value of case small in an enum with String raw values?

    • ""
    • "small"
    • 0
    • nil
  4. Which built-in Swift type is an enum?

    • String
    • Array
    • Int
    • Optional
  5. What must a method that changes self in an enum be marked?

    • static
    • mutating
    • override
    • final