Back to blog

Enums in Swift Explained


Aasif Khan
By Aasif Khan | Last Updated on November 21st, 2024 9:27 am | 4-min read

With Swift’s enums, also known as enumerations, you can organize groups of values that are related. They’re perfect for managing state in your app, and you can use them in a type-safe way in your code.

And that’s not everything: with associated values and raw values you can structure, manage and pass around bits of information in your code with little overhead or syntax!

In this tutorial, you learn:

  • How you can use enums in your own Swift code
  • Why enums are so useful, and practical use cases (pun intended)
  • What associated values and raw values are
  • How to use enums to design state in your app

What’s an Enum?

An enumeration (“enum”) is a group of values that are related. A good example are the directions on a compass. Like this:

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

Enumerations, or enums as they’re called, help you work with related values in a type-safe way. That means you can rely on type-checking, and that your enums have the same benefits as any other type in Swift.

The easiest way to think about enums is as structured lists of related items. A few examples:

  • Colors: red, green, blue, purple, yellow, etc.
  • Ice cream flavors: vanilla, chocolate, stracciatella, butter pecan, etc.
  • Log in states: .authenticated, .unauthenticated and .undetermined
  • Video player states: .playing, .paused, .stoppped and .idle
  • And more: day periods, months, day of the week, etcetera

The syntax for enumerations is quite simple:

  • You use the enum keyword to declare an enumeration, i.e. enum name { body }, much like declaring classes
  • In the enum body, you can include a number of cases with the case keyword. An enum’s cases are the different values that it has, such as the above ice cream flavors.
  • Enums can have associated types and raw values, but more on this later

You can also define enum cases on one line, like this:

case Weekday {
case monday, tuesday, wednesday, thursday, friday, saturday, sunday
}

And using an enum is quite simple, too. Here’s an example:

let today:Weekday = Weekday.monday

The above code is the same as this:

let today:Weekday = .monday

And it’s the same as this, too:

let today = Weekday.monday

See the differences? Thanks to Swift’s type inference you don’t have to explicitly declare the type of a variable or constant. And once an enum type has been set, you can use the shorter dot-syntax. Like this:

var direction:Compass = .north

// Your code does some other stuff …

direction = .south

You’ll often see this dot-syntax in practical iOS development, so it’s good to know how to recognize it.

Why don’t you give it a try? Use this sandbox:

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

var direction = Compass.north
print(direction)

What’s interesting about enums is that they can’t be changed once set. When you define an enum, like this:

enum AuthenticationState {
case authenticated
case unauthenticated
case undetermined
}

You can be certain that any value of type AuthenticationState is any of those three values; enums are exhaustive. It can’t be something else! This introduces type-safety in your code and results in fewer errors. You’ll also spot errors sooner, because the Swift compiler can check the types of enums before your code runs.

Let’s look at a few scenarios in which enums are useful.

Enums, States and Switch

Combining enums with the switch statement in Swift is particularly powerful. Perhaps you’ve heard about the concept of “state” before. In practical iOS development, we’re often managing the state of different systems and interactions in our app.

Imagine we’re coding a sentient robot. This robot has moods. As such, we’re modelling those emotions – which are states – with an enum. Like this:

enum Emotion {
case happy, sad, angry, scared, surprised
}

The robot we’re building has a mood property of type Emotion. We can now use the switch statement to respond to these emotions:

switch robot.mood {
case .angry:
robot.destroyAllHumans()
case .sad:
robot.rust() // Robots don’t cry, they rust
case .happy:
robot.play(“happy.mp3”)
case default:
print(“Error: emotion not supported.”)
}

See what happens here? We’re responding to the different cases of the Emotion enum. Every case in the switch block corresponds to a case in the enumeration. And because the switch statement needs to be exhaustive, we’re working with default for cases that aren’t explicitly handled.

What’s so cool about using enums and switch is that the code is exceptionally readable and concise. The amount of syntax overhead is minimal, especially compared to how you’d code this with if, else if and else.

Designing different states, called state modelling, is complicated enough to warrant a separate tutorial on that topic. One approach you can consider right now is how often you’re using boolean flags in your code.

Consider the following scenario. A user can give permission to use the iPhone camera in your app, and you’ve modeled the different states with two boolean properties. Like this:

var hasGivenPermission:Bool = false
var permissionGranted:Bool = false

You’re accounting for two states: if the user has given permission before, and if permission has been granted. That way you can ensure you’re only asking for permission once.

The above state can be more effectively modeled with an enumeration:

enum Permission {
case undetermined
case granted
case denied
}

Modeling state like this is clearer, less prone to errors, and easier to manage. You can easily add another state, and you have the advantages of type-safety.

Let’s look at a feature of enums that makes them convey even more information: associated values.

Associated Values in Enums

Enums let you assign one of its predetermined values to a variable or a constant, that much is clear. What you can do on top of that, is associating values with the individual cases of an enum.

Imagine we’re creating an Role Playing Game (RPG) app. The player can add several types of items to their inventory. Every kind of item has different properties. For example:

  • Weapons have hitpoints and weight
  • Food has health points
  • Armor has a damage threshold, weight and condition

Here’s how that looks with an enum and associated values:

enum Item {
case weapon(Int, Int)
case food(Int)
case armor(Int, Int, Double)
}

New values of enum Item can be created with any of these three cases. And every case has a few extra types written in parentheses, called a tuple. You can assign any kind of value to them.

Imagine you’re writing a function to use a particular item from the RPG player’s inventory. Like this:

func use(item: Item)
{
switch item {
case .weapon(let hitPoints, _):
player.attack(hitPoints)
case .food(let health):
player.health += health
case .armor(let damageThreshold, let weight, let condition):
player.damageThreshold = Double(damageThreshold) * condition
}
}

In the above code you’re breaking up the values of the tuples in separate constants. See how the constant hitPoints corresponds to the first Int value in the enum case .weapon(Int, Int)?

When you want to access every associated value of a case, you can also use a single let. Like this:

switch item {
case let .armor(damageThreshold, weight, condition):
player.damageThreshold =
}

You can also do interesting things with pattern matching based on associated values. Like this:

let item = Item.armor(15, 10, 0.75)

switch item {
case let .armor(damageThreshold, weight, condition) where weight < 10: print("DT = \(Double(damageThreshold) * condition)") case .armor: print("This armor is too heavy for you!") default: print("This item is not armor.") } In the above code we’re using the where keyword to partially match .armor cases where weight is smaller than 10. In the second case, we’re matching any .armor case that doesn’t match the first case, i.e. any weight greater or equal to 10. And the default case is there to match anything that’s not armor. Enumerations with associated values are common in Swift these days. Take for instance the Result enum from Alamofire, the popular HTTP networking library. public enum Result {
case success(Value)
case failure(Error)

public var value: Value? {
switch self {
case .success(let value):
return value
case .failure:
return nil
}
}
}

The enum Result uses the generic Value and Error protocol to represent two possible states: a result from a successful or failed request. When the request has succeeded, we can get a Value, and when it has failed, we can get an Error.

The value computed property of the Result enum provides an optional Value? when called. It uses the switch statement to either return a value or return nil.

OK, let’s move on to one last topic: raw values.

Using Raw Values of Enums

Instead of associated values, enums can have raw values. Here’s an example:

enum Flavor:String {
case vanilla = “vanilla”
case strawberry = “strawberry”
case chocolate = “chocolate”
}

The raw value of Flavor is String. Every case of Flavor comes prepopulated with string values, such as “vanilla”. Raw values can be strings, characters, or any of the integer or floating-point number types.

Let’s see how you can work with raw values. Here’s an example:

let icecream = Flavor.vanilla
print(icecream.rawValue)
// Output: vanilla

In the above example, the type of icecream is Flavor and the type of icecream.rawValue is String.

And we can also construct a value of Flavor straight from its raw String value. Like this:

let icecream = Flavor(rawValue: “vanilla”)
print(icecream)
// Output: Optional(Flavor.vanilla)

Keep in mind that the raw value initializer returns an optional, in case you provide a raw value that cannot be represented by the enum.

A particularly powerful use case for raw values in enums is when you’re working with enum-like values that are represented by strings.

Consider for instance that an API returns a user.level value that indicates the access level a particular user has. The API uses JSON to get the data to you, and as such the enum uses strings.

Here’s the enumeration:

enum AuthLevel: String {
case admin = “admin”
case moderator = “mod”
case contributor = “contrib”
case user = “user”
}

You get the string value from the API, translate it to an AuthLevel enum case with its raw value, and you get the benefits of working with enums in your own code. Like this:

let level = AuthLevel(rawValue: json[“user”][“level”])

You can even provide a default value in case the initializer fails, assuming you’ve added a case none to the enum. Like this:

let level = AuthLevel(rawValue: “james_bond”) ?? .none
print(level)
// Output: none

And to make matters easier, raw values can also get assigned implicitly. Like this:

enum Weekday: Int {
case monday = 1, tuesday, wednesday, thursday, friday, saturday, sunday
}

In the above example, the raw value of .sunday is 7. And it works similarly for strings:

enum Compass:String {
case north, east, south, west
}

let direction = Compass.west
print(direction.rawValue == “west”)
// Output: true

In the above example, the raw values of Compass are strings. They directly represent the names of the cases, i.e. .north equals the string “north”.

Further Reading

Who would have thought that something as simple as an enum could be so interesting? Thanks to enum’s type-safety, you can safely wrangle any group of related values that come your way.

Enumerations are useful for dealing with states, and you can quickly respond to different scenarios by using switch. And if that’s not enough, you can spice up your enums with raw values and associated values.


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts