Give your simulator superpowers

RocketSim: An Essential Developer Tool
as recommended by Apple

Enum explained in-depth with code examples in Swift

Enum usage in Swift: If case, guard case, fallthrough, associated values, and the CaseIteratable protocol. These terms could sound familiar if you’ve worked extensively with Swift enums. An enumeration defines a common type for a group of related values and enables you to work with those values in a type-safe way within your code.

It’s essential to be aware of enums’ capabilities to improve your code’s readability. You’ll benefit from compile-time safety, allowing fewer bugs to appear. Let’s go over them in more detail.

Defining an enum

Chances are high that you’ve worked with enums before. The text alignment enum is a well-known example from SwiftUI’s standard library:

enum TextAlignment {
    case leading
    case center
    case trailing
}

The above example defines three different cases: leading, center, and trailing. You can use these in SwiftUI by using the multiline text alignment modifier:

VStack {
    Text("Hello, world!")
}.multilineTextAlignment(.center)

In this case, we aligned the text to the center if we used multiple lines. The enum improves readability and benefits us from compile-time safety. Enums are great if your method only works with a single case. For example, we can’t align text to the leading and center edges. If you need more options, you can look at OptionSet in Swift explained with code examples.

You can also create your enum, giving it a unique name that does not clash with other types. For example, I prefer making an enum for my onboarding pages, like this one for RocketSim:

enum OnboardingPage {
    case welcome
    case selectXcode
    case featuresExplanation
}

In this example, we defined the cases underneath each other. However, you could also define the enum as follows:

enum OnboardingPage {
    case welcome, selectXcode, featuresExplanation
}

Defining an enum’s raw value

By default, an enum doesn’t come with a raw value. You can add one by adding an inheritance definition, for example, using a String type:

enum OnboardingPage: String {
    case welcome
    case selectXcode
    case featuresExplanation
}

Doing so allows you to use the case name as a title value:

Text(OnboardingPage.welcome.rawValue) // Shows: "welcome"

As you can see, the case name welcome converted to a string value “welcome”.

You might want to adjust the title for particular cases in which you can override the raw value as follows:

enum OnboardingPage: String {
    case welcome
    case featuresExplanation
    case selectXcode = "Select Xcode"
}

However, sometimes it might be smarter to define a property using a self-explaining name:

enum OnboardingPage: String, CaseIterable {
    case welcome
    case featuresExplanation
    case selectXcode

    var title: String {
        switch self {
        case .welcome:
            return "Welcome to RocketSim"
        case .featuresExplanation:
            return "RocketSim's Xcode Simulator Features"
        case .selectXcode:
            return "Select Xcode"
        }
    }
}

We defined a new property title and used a switch-case statement to specify a title for all cases. The compiler will show an error if you forget to handle one of the cases. In case you don’t want to define a specific title for each case, you can choose to use the default statement:

var title: String {
    switch self {
    case .welcome:
        return "Welcome to RocketSim"
    case .selectXcode:
        return "Select Xcode"
    default:
        return rawValue
    }
}

In this case, I’ve returned the raw string value as a default title. This is not ideal, as it will return “featuresExplanation” for the featuresExplanation case. However, you might have scenarios where a case name directly converts well into a readable title.

You’re only required to define the default case if you’re not yet iterating over all cases. Otherwise, you’ll see a warning mentioning “Default will never be executed”.

How do you stay current as a Swift developer?

Let me do the hard work and join 19,144 developers that stay up to date using my weekly newsletter:

Iterating over all enum cases

Enums become especially powerful once you use the CaseIterable protocol to iterate over all cases. You’ll first need to add the protocol to your enum definition:

enum OnboardingPage: String, CaseIterable {
    case welcome
    case featuresExplanation
    case selectXcode = "Select Xcode"
}

After adding the protocol, you can access a new allCases static property to access all defined cases:

OnboardingPage.allCases.forEach { onboardingPage in
    print(onboardingPage.rawValue)
}

// Prints:
// welcome
// featuresExplanation
// Select Xcode

Swift Enum Count: Getting the total number of cases

The allCases property returns a collection type, allowing you to perform actions like forEach, filter, and map. It also allows you to read out the enum count of total cases, making it possible to inform your users about the expected number of onboarding pages:

struct OnboardingView: View {
    var body: some View {
        VStack {
            Text("Welcome")
            Text("This onboarding contains \(OnboardingPage.allCases.count) pages.")
        }
    }
}

The best of all: you can iterate over all cases and use a paged tab view to create your dynamic onboarding:

struct OnboardingView: View {
    var body: some View {
        VStack {
            Text("Welcome")
            Text("This onboarding contains \(OnboardingPage.allCases.count) pages.")

            TabView {
                ForEach(OnboardingPage.allCases, id: \.self) { onboardingPage in
                    Text(onboardingPage.rawValue)
                        .font(.title)
                }
            }.tabViewStyle(.page)
        }
    }
}

Enums and Equatable

Enums are comparable by default, so you don’t have to add the Equatable protocol conformance:

let pageOne: OnboardingPage = .welcome
let pageTwo: OnboardingPage = .selectXcode

print(pageOne == pageTwo) // Prints: false

Enum with parameters and conforming to Equatable

However, you can also define enums with associated values:

enum Delay {
    case seconds(Int)
    case milliseconds(Int)
    case microseconds(Int)
    case nanoseconds(Int)
}

Swift automatically provides Equatable conformance for enums if their associated values also conform to the Equatable protocol. For standard types, this conformance is included by default. However, for custom types, you may need to implement Equatable conformance manually.

Imagine having an enum with a case that has a custom type UserInfo as one of its associated values:

struct UserInfo {
    let id: Int
    let name: String
}

enum AppNotification {
    case message(text: String)
    case friendRequest(from: UserInfo)
    case systemAlert(title: String, description: String)
}

Since the UserInfo structure doesn’t conform to Equatable, we can’t compare the enum cases at all:

Swift Enum cases are only equatable if all associated values are.
Swift Enum cases are only equatable if all associated values are.

More specifically, the error reads as follows:

Binary operator ‘==’ cannot be applied to two ‘AppNotification’ operands

To resolve this, we make the UserInfo struct conform to Equatable and explicitly declare the enum’s Equatable conformance:

struct UserInfo: Equatable {
    let id: Int
    let name: String
}

enum AppNotification: Equatable {
    case message(text: String)
    case friendRequest(from: UserInfo)
    case systemAlert(title: String, description: String)
}

If case, guard case without Equatable

If your enum contains values of many different types, it might not be easy to inherit from the Equatable protocol. This is the case when the inner types do not conform to Equatable. It’s also the case with the Result enum, which contains generic values.

enum Result<Success, Failure> where Failure: Error {
    case success(Success)
    case failure(Error)
}

If you’re new to the result enum, I encourage you to read Result in Swift: Getting started with Code Examples.

Both Value and Error are generic values and can be of any type. Since we can’t predict the final type of those values, we can also not add equatable conformance.

In these scenarios, you can use the if case or guard case statements. For example, you can add an extension to allow easy access to the inner values:

extension Result {
    /// The error in case the result was a failure
    public var error: Error? {
        guard case .failure(let error) = self else { return nil }
        return error
    }

    /// The value in case the result was a success result.
    public var value: Success? {
        guard case .success(let value) = self else { return nil }
        return value
    }
}

The extension defines two properties to access the value and error easily. Guard case can be used just like if case, which makes it possible to read the inner value of the enum case:

/// The value in case the result was a success result.
public var value: Success? {
    if case .success(let value) = self {
        return value
    } else {
        return nil
    }
}

Using fallthrough in a switch statement

The fallthrough keyword causes program execution to continue from one case in a switch statement to the next case. This can be handy if you’re printing out a type like in the following example:

enum ImageType {
    case jpeg
    case png
    case gif
}

let imageTypeToDescribe = ImageType.gif

var description = "The image type \(imageTypeToDescribe) is"

switch imageTypeToDescribe {
case .gif:
    description += " animatable, and also"
    fallthrough
default:
    description += " an image."
}

print(description) // The image type gif is animatable, and also an image.

For JPEG and PNG, we will only see a print statement containing “an image” in its tail, while the GIF print statement will also explain that it’s an animatable image.

Using an enum in a ForEach in SwiftUI

A common use case is to use enums as an argument for a ForEach in SwiftUI. This allows you to show a view for each case. We’ve demonstrated such example in this article already:

ForEach(OnboardingPage.allCases, id: \.self) { onboardingPage in
    Text(onboardingPage.title)
        .font(.title)
}

In this example, we’re using .self as an id for the iteration. You can also decide to implement the Identifiable protocol for the enum:

enum OnboardingPage: String, CaseIterable, Identifiable {
    case welcome
    case featuresExplanation
    case selectXcode
    
    /// Use the rawValue as the identifier for this enum.
    var id: String { rawValue }

    var title: String {
        switch self {
        case .welcome:
            return "Welcome to RocketSim"
        case .featuresExplanation:
            return "RocketSim's Xcode Simulator Features"
        case .selectXcode:
            return "Select Xcode"
        }
    }
}

After defining the id property, you no longer have to define the id parameter inside the ForEach:

ForEach(OnboardingPage.allCases) { onboardingPage in
    Text(onboardingPage.title)
        .font(.title)
}

Option sets in Swift

Similar to enums in usage, you can also define an OptionSet in Swift. The major difference is that they allow you to pass in multiple cases as options. You can read more about this in my article OptionSet in Swift explained with code examples.

Conclusion

Enums are a powerful tool when writing code in Swift. They improve readability while making you benefit from compile-time safety. You can create dynamic code solutions using the CaseIterable protocol and custom raw values. Enums are comparable by default unless you’ve added associated values.

If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!

 
Antoine van der Lee

Written by

Antoine van der Lee

iOS Developer since 2010, former Staff iOS Engineer at WeTransfer and currently full-time Indie Developer & Founder at SwiftLee. Writing a new blog post every week related to Swift, iOS and Xcode. Regular speaker and workshop host.

Are you ready to

Turn your side projects into independence?

Learn my proven steps to transform your passion into profit.