Chapters

Hide chapters

Swift Apprentice: Fundamentals

First Edition · iOS 16 · Swift 5.7 · Xcode 14.2

Section III: Building Your Own Types

Section 3: 9 chapters
Show chapters Hide chapters

16. Enumerations
Written by Ehab Amer

One day in your life as a developer, you realize you’re being held captive by your laptop. Determined to break from convention, you set off on a long trek on foot. Of course, you need a map of the terrain you’ll encounter. Since it’s the 21st century and you’re fluent in Swift, you decide to create a custom map app.

As you code away, you think it would be swell to represent the cardinal directions as variables: north, south, east and west. But what’s the best way to do this in code?

You could represent each value as an integer, like so:

  • North: 1
  • South: 2
  • East: 3
  • West: 4

This encoding could quickly get confusing if you or your users happen to think of the directions in a different order. “What does 3 mean again?” To alleviate that, you might represent the values as strings, like so:

  • North: "north"
  • South: "south"
  • East: "east"
  • West: "west"

The trouble with strings, though, is that the value can be any string. What would your app do if it received "up" instead of "north"? Furthermore, it’s all too easy to make a typo like "nrth".

Wouldn’t it be great if there were a way to create a group of related, compiler-checked values? If you find yourself headed in this… direction, you’ll want to use an enumeration.

An enumeration is a list of related values that define a common type and let you work with values in a type-safe way. The compiler will catch your mistake if your code expects a Direction and you try to pass in a float like 10.7 or a misspelled direction like "Souuth".

Besides cardinal directions, other good examples of related values are colors (black, red, blue), card suits (hearts, spades, clubs, diamonds) and roles (administrator, editor, reader).

Enumerations in Swift are more powerful than they are in other languages, such as C or Objective-C. They share features with the structure and class types you learned about in Chapter 11, “Structures”, and Chapter 14, “Classes”. An enumeration can have methods and computed properties while holding a particular state.

In this chapter, you’ll learn how enumerations work and when they’re useful. As a bonus, you’ll finally discover what an optional is under the hood. Hint: They are implemented with enumerations!

Your First Enumeration

Your challenge: Construct a function to determine the school semester based on the month. One way to solve this would be to use an array of strings and match the semesters with a switch statement:

let months = ["January", "February", "March", "April", "May",
              "June", "July", "August", "September", "October",
              "November", "December"]

func semester(for month: String) -> String {
  switch month {
  case "August", "September", "October", "November", "December":
    return "Autumn"
  case "January", "February", "March", "April", "May":
    return "Spring"
  default:
    return "Not in the school year"
  }
}

semester(for: "April") // Spring

Running this code in a playground, you can see that the function correctly returns "Spring". But as you saw in the introduction, it’s easy to mistype a string. A better way to tackle this would be with an enumeration.

Declaring an Enumeration

To declare an enumeration, you list out all the possible member values as case clauses:

enum Month {
  case january
  case february
  case march
  case april
  case may
  case june
  case july
  case august
  case september
  case october
  case november
  case december
}

This code creates a new enumeration called Month with 12 possible member values. The commonly accepted best practice is to start each member value with a lowercase first letter, just like a property.

You can simplify the code a bit by collapsing the case clauses down to one line, with each value separated by a comma:

enum Month {
  case january, february, march, april, may, june, july, august,
  september, october, november, december
}

That looks snazzy and simple. So far, so good.

Deciphering an Enumeration in a Function

You can rewrite the function that determines the semester to use enumeration values instead of string matching.

func semester(for month: Month) -> String {
  switch month {
  case Month.august, Month.september, Month.october,
       Month.november, Month.december:
    return "Autumn"
  case Month.january, Month.february, Month.march,
       Month.april, Month.may:
    return "Spring"
  default:
    return "Not in the school year"
  }
}

Since Swift is strongly typed and uses type inference, you can simplify semester(for:) by removing the enumeration name in places where the compiler already knows the type. Keep the dot prefix, but lose the enumeration name, as shown below for the cases inside the switch statement:

func semester(for month: Month) -> String {
  switch month {
  case .august, .september, .october, .november, .december:
    return "Autumn"
  case .january, .february, .march, .april, .may:
    return "Spring"
  default:
    return "Not in the school year"
  }
}

Also, recall that switch statements must be exhaustive with their cases. The compiler will warn you if they aren’t. When case patterns are String elements, you need a default case because it’s impossible to create cases to match every possible String value.

However, enumerations have a limited set of values you can match against. So if you have cases for each member value of the enumeration, you can safely remove the default case of the switch statement:

func semester(for month: Month) -> String {
  switch month {
  case .august, .september, .october, .november, .december:
    return "Autumn"
  case .january, .february, .march, .april, .may:
    return "Spring"
  case .june, .july:
    return "Not in the school year"
  }
}

That’s much more readable. There is another huge benefit to getting rid of the default. If in a future update, someone added .undecember or .duodecember to the Month enumeration, the compiler would automatically flag this and any other switch statement as being non-exhaustive, allowing you to handle this specific case.

You can test this function in a playground like so:

var month = Month.april
semester(for: month) // "Spring"

month = .september
semester(for: month) // "Autumn"

The variable declaration for month uses the full enumeration type and value. You can use the shorthand .september in the second assignment since the compiler already knows the type. Alternatively, you could have strongly typed the variable using var month: Month = .april if you find that easier to read. Finally, you pass both months to semester(for:), where a switch statement returns the strings "Spring" and "Autumn" respectively.

Mini-Exercise

Wouldn’t it be nice to request the semester from an instance like month.semester instead of using the function? Add a semester computed property to the month enumeration so that you can run this code:

let semester = month.semester // "Autumn"

Using Code Completion to Prevent Typos

Another advantage of using enumerations instead of strings is that you’ll never have a typo in your member values. Xcode provides code completion:

And if you do misspell an enumeration value, the compiler will complain with an error, so you won’t get too far down the line without recognizing your mistake:

Raw Values

Unlike enumeration values in C, Swift enum values are not backed by integers as a default. That means january is itself the value.

You can specify that an integer backs the enumeration by declaring it with : Int like this:

enum Month: Int {

Swift enumerations are flexible: you can specify other raw value types like String, Float or Character. As in C, if you use integers and don’t specify values as you’ve done here, Swift will automatically assign the values 0, 1, 2 and up.

In this case, it would be better if January had the raw value of 1 rather than 0. To specify your own raw values, use the = assignment operator:

enum Month: Int {
  case january = 1, february = 2, march = 3, april = 4, may = 5,
  june = 6, july = 7, august = 8, september = 9,
  october = 10, november = 11, december = 12
}

This code assigns an integer value to each enumeration case.

There’s another handy shortcut here. The compiler will automatically increment the values if you provide the first one and leave out the rest:

enum Month: Int {
  case january = 1, february, march, april, may, june, july,
  august, september, october, november, december
}

You can use the enumeration values alone and never refer to the raw values if you don’t want to. But the raw values will be there behind the scenes if you ever need them!

Accessing the Raw Value

Enumeration instances with raw values have a handy rawValue property. With the raw values in place, your enumeration has a sense of order, and you can calculate the number of months left until winter break:

func monthsUntilWinterBreak(from month: Month) -> Int {
  Month.december.rawValue - month.rawValue
}
monthsUntilWinterBreak(from: .april) // 8

Initializing With the Raw Value

You can use the raw value to instantiate an enumeration value with an initializer. You can use init(rawValue:) to do this, but if you try to use the value afterward, you’ll get an error:

let fifthMonth = Month(rawValue: 5)
monthsUntilWinterBreak(from: fifthMonth) // Error: not unwrapped

There’s no guarantee that the raw value you pass in exists in the enumeration, so the initializer can fail. An optional value expresses this possibility for failure. For example, you could have used 13 as the input for a month that does not exist. Enumeration initializers with the rawValue: parameter are failable initializers, meaning if things go wrong, the initializer will return nil.

If you’re using these raw value initializers in your own projects, remember that they return optionals. If you’re unsure if the raw value is correct, you’ll need to either check for nil or use optional binding. In this case, the value 5 must be correct, so it’s appropriate to force unwrap the optional:

let fifthMonth = Month(rawValue: 5)! // may
monthsUntilWinterBreak(from: fifthMonth) // 7

That’s better! You used the exclamation mark, !, to force unwrap the optional. Now there’s no error, and monthsUntilWinterBreak(from:) returns 7 as expected.

Mini-Exercise

Make monthsUntilWinterBreak a computed property of the Month enumeration so that you can execute the following code:

let monthsLeft = fifthMonth.monthsUntilWinterBreak // 7

String Raw Values

Similar to the handy trick of incrementing an Int raw value, if you specify a raw value type of String, you’ll get another automatic conversion. Pretend you’re building a news app that has tabs for each section. Each section has an icon. Icons are a good opportunity to deploy enumerations because, by their nature, they are a limited set:

// 1
enum Icon: String {
  case music
  case sports
  case weather

  var filename: String {
    // 2
    "\(rawValue).png"
  }
}
let icon = Icon.weather
icon.filename // weather.png

Here’s what’s happening in this code:

  1. The enumeration declares Icon with a String raw value type.
  2. Calling rawValue inside the enumeration definition is equivalent to calling self.rawValue. Since the raw value is a string, you can use it to build a file name.

Note you didn’t have to specify a String for each member value. If you set the raw value type of the enumeration to String and don’t specify any raw values yourself, the compiler will use the enumeration case names as the raw values. The filename computed property will generate an image asset name for you. You can now fetch and display images for the tab icons in your app.

Next, let’s jump back to working with raw numerical values and learn how to use enumerations for banking.

Unordered Raw Values

Integer raw values don’t have to be in an incremental order. Coins are a good use case:

enum Coin: Int {
  case penny = 1
  case nickel = 5
  case dime = 10
  case quarter = 25
}

You can instantiate values of this type and access their raw values as usual:

let coin = Coin.quarter
coin.rawValue // 25

let aSmallCoin = Coin.dime
coin.rawValue > aSmallCoin.rawValue   //true

aSmallCoin.rawValue + coin.rawValue   //35

It’s important to understand that the Coin enum is not an Int; it just has Int raw values. You will get a compiler error if you try to add two Coin variables, but you can add their raw values:

Mini-Exercise

Create an array called coinPurse that contains coins. Add an assortment of pennies, nickels, dimes and quarters to it.

Associated Values

Associated values take Swift enumerations to the next level in expressive power. They let you associate a custom value (or values) with each enumeration case.

Here are some unique qualities of associated values:

  1. Each enumeration case has zero or more associated values.
  2. The associated values for each enumeration case have their own data type.
  3. You can define associated values with label names as you would for named function parameters.

An enumeration can have raw values or associated values, but not both.

In the last mini-exercise, you defined a coin purse. Let’s say you took your money to the bank and deposited it. You could then go to an ATM and withdraw your money:

var balance = 100

func withdraw(amount: Int) {
  balance -= amount
}

The ATM will only let you withdraw what you put in, so it needs a way to let you know whether the transaction was successful. You can implement this as an enumeration with associated values:

enum WithdrawalResult {
  case success(newBalance: Int)
  case error(message: String)
}

Each case has a required value to go along with it. For the success case, the associated Int will hold the new balance; for the error case, the associated String will have some kind of error message.

Then you can rewrite the withdraw function to use the enumeration cases:

func withdraw(amount: Int) -> WithdrawalResult {
  if amount <= balance {
    balance -= amount
    return .success(newBalance: balance)
  } else {
    return .error(message: "Not enough money!")
  }
}

Now you can perform a withdrawal and handle the result:

let result = withdraw(amount: 99)

switch result {
case .success(let newBalance):
  print("Your new balance is: \(newBalance)")
case .error(let message):
  print(message)
}

Notice how you used let bindings to read the associated values. Associated values aren’t properties you can access freely, so you’ll need bindings like these to read them.

Remember that the newly bound constants newBalance and message are local to the switch cases. They aren’t required to have the same name as the associated values, although it’s common to do so.

This prints out the following in the debug console:

Your new balance is: 1

Many real-world contexts function by accessing associated values in an enumeration. For example, internet servers often use enumerations to differentiate between types of requests:

enum HTTPMethod {
  case get
  case post(body: String)
}

In the bank account example, you had multiple values you wanted to check for in the enumeration. In places where you only have one, you could instead use pattern matching in an if case or guard case statement. Here’s how that works:

let request = HTTPMethod.post(body: "Hi there")
guard case .post(let body) = request else {
  fatalError("No message was posted")
}
print(body)

In this code, guard case checks to see if request contains the post enumeration case and, if so, reads and binds the associated value.

You’ll also see enumerations used in error handling. The bank account example had multiple cases but one generic error case with an associated string.

Enumeration as a State Machine

An enumeration is an example of a state machine, meaning it can only ever be a single case at a time, never more. The friendly traffic light illustrates this concept well:

enum TrafficLight {
  case red, yellow, green
}
let trafficLight = TrafficLight.red

A working traffic light will never be red and green simultaneously. You can observe this state machine behavior in other modern devices that follow a predetermined sequence of actions in response to events.

Examples of state machines include:

  • Vending machines that dispense soda when the customer deposits the proper amount of money.
  • Elevators that drop riders off at upper floors before going down.
  • Combination locks that require combination numbers in the proper order.

To operate as expected, these devices depend on an enumeration’s guarantee that they will only ever be in one state at a time.

Mini-Exercise

A household light switch is another example of a state machine. Create an enumeration for a light that can switch .on and .off.

Iterating Through All Cases

Sometimes you want to loop through all of the cases in an enumeration. This is easy to do:

enum Pet: CaseIterable {
  case cat, dog, bird, turtle, fish, hamster
}

for pet in Pet.allCases {
  print(pet)
}

When you conform to the CaseIterable protocol, your enumeration gains a class method called allCases that lets you loop through each case in the order it was declared. This prints:

cat
dog
bird
turtle
fish
hamster

Enumerations Without Any Cases

In Chapter 13, “Methods,” you learned how to create a namespace for a group of related type methods. The example in that chapter looked like this:

struct Math {
  static func factorial(of number: Int) -> Int {
    (1...number).reduce(1, *)
  }
}
let factorial = Math.factorial(of: 6) // 720

One thing you may not have realized at the time is that you could create an instance of Math, like so:

let math = Math()

The math instance doesn’t serve any purpose since it is empty; it has no stored properties. In situations like this, the better design is actually to transform Math from a structure to an enumeration:

enum Math {
  static func factorial(of number: Int) -> Int {
    (1...number).reduce(1, *)
  }
}
let factorial = Math.factorial(of: 6) // 720

Now, if you try to make an instance, the compiler will give you an error:

Enumerations with no cases are sometimes referred to as uninhabited types or bottom types.

As you learned at the beginning of this chapter, enumerations are powerful. They can do almost everything a structure can, including having custom initializers, computed properties and methods. To create an enumeration instance, though, you have to assign a member value as the state. If there are no member values, you won’t be able to create an instance.

That works perfectly for you in this case (pun intended). There’s no reason to have an instance of Math. You should make the design decision that there will never be an instance of the type.

That will prevent future developers from accidentally creating an instance and help enforce its use as you intended. So, choose a case-less enumeration if it would be confusing if a valueless instance existed.

Mini-Exercise

Euler’s number is useful in calculating statistical bell curves and compound growth rates. Add the constant e, 2.7183, to your Math namespace. Then you can figure out how much money you’ll have if you invest $25,000 at 7% continuous interest for 20 years:

let nestEgg = 25000 * pow(Math.e, 0.07 * 20) // $101,380.95

Note: In everyday life, you should use M_E from the Foundation library for the value of e. The Math namespace here is just for practice.

Optionals

Since you’ve made it this far, the time has come to let you in on a little secret. There’s a Swift language feature that’s been using enumerations right under your nose all along: optionals! In this section, you’ll explore their underlying mechanism.

Optionals act like containers that have either something or nothing inside:

var age: Int?
age = 17
age = nil

Optionals are enumerations with two cases:

  1. .none means there’s no value.
  2. .some means a value attached to the enumeration case as an associated value.

You can extract the associated value from an optional with a switch statement, as you’ve already seen:

switch age {
case .none:
  print("No value")
case .some(let value):
  print("Got a value: \(value)")
}

You’ll see this printed to the debug console:

No value

Although optionals are enumerations under the hood, Swift hides the implementation details by using optional binding, the ? and ! operators, and keywords such as nil.

let optionalNil: Int? = .none
optionalNil == nil    // true
optionalNil == .none  // true

If you try this in a playground, you’ll see that nil and .none are equivalent.

In Chapter 18, “Generics,” you’ll learn a bit more about the underlying mechanism for optionals, including how to write your code to function in the same manner as optionals.

Now that you know how optionals work, you’ll have the right tool for the job the next time you need a value container.

Challenges

Before moving on, here are some challenges to test your knowledge of enumerations. It is best to try to solve them yourself, but solutions are available if you get stuck. These came with the download or are available at the printed book’s source code link listed in the introduction.

Challenge 1: Adding Raw Values

Take the coin example from earlier in the chapter and begin with the following array of coins:

enum Coin: Int {
  case penny = 1
  case nickel = 5
  case dime = 10
  case quarter = 25
}

let coinPurse: [Coin] = [.penny, .quarter, .nickel, .dime, .penny, .dime, .quarter]

Write a function where you can pass in the array of coins, add up the value and then return the number of cents.

Challenge 2: Computing With Raw Values

Take the example from earlier in the chapter and begin with the Month enumeration:

enum Month: Int {
  case january = 1, february, march, april, may, june, july,
       august, september, october, november, december
}

Write a computed property to calculate the number of months until summer.

Hint: You’ll need to account for a negative value if summer has already passed in the current year. To do that, imagine looping back around for the next full year.

Challenge 3: Pattern Matching Enumeration Values

Take the map example from earlier in the chapter and begin with the Direction enumeration:

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

Imagine starting a new level in a video game. The character makes a series of movements in the game. Calculate the position of the character on a top-down level map after making a set of movements:

let movements: [Direction] = [.north, .north, .west, .south,
  .west, .south, .south, .east, .east, .south, .east]

Hint: Use a tuple for the location:

var location = (x: 0, y: 0)

Key Points

  • An enumeration is a list of mutually exclusive cases that define a common type.
  • Enumerations provide a type-safe alternative to old-fashioned integer values or strings.
  • You can use enumerations to handle responses, store state and encapsulate values.
  • CaseIterable lets you loop through an enumeration with allCases.
  • Uninhabited enumerations can be used as namespaces and prevent the creation of instances.
  • The Swift Optional type is a generic enumeration with cases .none and .some.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.