Give your simulator superpowers

Give your Xcode
Simulator extra features

Enum explained in-depth with code examples in Swift

Enum usage in Swift: If case, guard case, fallthrough, and the CaseIteratable protocol. These are all terms which could sound familiar if you’ve worked a lot with enums in Swift. 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:

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 in case we’re using multiple lines. The enum improves readability and allows us to benefit 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 creating an enum for my onboarding pages, like this one for RocketSim:

enum OnboardingPage {
    case welcome
    case grids
    case rulers
}

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

enum OnboardingPage {
    case welcome, grids, rulers
}

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 grids
    case rulers
}

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 grids
    case rulers
    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 grids
    case rulers
    case selectXcode

    var title: String {
        switch self {
        case .welcome:
            return "Welcome to RocketSim"
        case .grids:
            return "Grids to align"
        case .rulers:
            return "Rulers for precision"
        case .selectXcode:
            return "Select Xcode"
        }
    }
}

We defined a new property title and used a switch-case statement to define 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 .rulers:
        return "Rulers for precision"
    case .selectXcode:
        return "Select Xcode"
    default:
        return rawValue
    }
}

I’ve returned the raw string value as a default title in this case.

Iterating over all enum cases

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

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

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

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

// Prints:
// welcome
// grids
// rulers
// Select Xcode

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 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 = .grids

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

However, you can also define enums with associated values:

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

Swift will support equatable conformance for these enums in the future as well, but at this moment you’ll run into the following error:

Enums are not equatable by default when they contain an associated value.
Enums are not equatable by default when they contain an associated value.

More specifically, the error reads as follows:

Binary operator ‘==’ cannot be synthesized for enums with associated values

We can solve this by adding explicit equatable conformance:

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

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 are not conforming to Equatable. It’s also the case with the popular 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.

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!