top of page

Swift Enums - A story about choices



Introduction

Swift enums are powerful. I'll explain a few basic usages and then step things up with less apparent uses that will change how you write your code, providing more type safety and simplifying code that would otherwise create a spaghetti-noodle mess. 

What if you had to create code that needed to evaluate two conditions and react accordingly with expressions ("then here's what I do code")? The base if-else formula handles this nicely. All is good when there are only two or a handful of conditions for which you want to react. You can even evaluate string matches as the condition. 


But there is a gotcha when testing for string equality with conditional branching that will bite you at some point during your coding adventures. In fact, several side effects could be hidden time bombs lying in wait, ready to jump you in the dark to capitalize on your typos and small mistakes, any of which could be hard to spot at first glance. When it happens to you, you'll be perturbed that the if-else formula you have grown so accustomed to writing betrayed you.


However, there is another way to reduce the chances of writing conditional code that is clearer to read and less error-prone. I'm curious. Skeptical. I thought so, but work with me here. It's not a catchall but a new tool for the toolbelt. 

A Swift enumeration (enum for short) is an efficient way to group a series of choices or categories to make it impossible to select another but something in the group of choices. Enumerations do the exact thing that Booleans does when choosing between two alternatives. It's on or off, but not on and off. Enums let you expand your choices to as many as you want and ensure that you are only ever dealing with your predefined choices.


Story time.

Full of adventure, Teddy goes on a hike into the woods carrying his backpack with his lunch, a map, and a Swiss army knife. Halfway into his hike, he is surprised that he missed the signs and finds himself surrounded by a bear, a wolf, and a skunk; all think he or his lunch looks very enticing. Teddy faces four choices:

  1. Feed the bear: Clearly, with the most enormous appetite.

  2. Feed the wolf: The cleverest of the lot, scheming already on satisfying hunger.

  3. Feed the Skunk: Small but potent. Feed this little guy because why not? Teddy already has a slight chance of survival anyway.

  4. Play Dumb: Audition for a Disney movie for the role of a lifetime, "Still Tree You Can't See Me"


As the seconds mount, Teddy has to make a decision. The suspense is thick in the air (and not just for air samples the skunk releases to alert his presence). 

Before I tell you how the story ends, let's look at some code to use enums to model this scenario using Swift Enums. I have added a link to the Swift Playground for you to review later.


To define an enum in Swift, you use the enum key followed by the name you want to use. You define your choices using case statements.

enum AnimalSurvival {
    case FeedBear
    case FeedWolf
    case FeedSkunk
    case PlayDumb
}

We can create a variable in the normal ways and define the type as our enum type and initialize it to a value.

var myImpossibleChoice: AnimalSurvival = .PlayDumb

We now have type safety for our choice of AnimalSurvival. Let's add a label to display the consequence of a choice made. We can use a switch statement on our enum to compare our options to our selected choice. Switch statements match a defined enum value to the available cases in the enum definition. We only choose from the options we defined in the enum. If we had used String values, we could easily create a variable that doesn't match our expected conditions using if-else conditions and end up with unexpected results.

let animalSurvival = "Feed the Bear" // <- the one we want
let chooseBear = "Feed Bear". // <- won't match due to missing a word
let chooseBearAgain = "Feed Bear " // <- won't match because trailing space
let choosePossum = "Choose Possum" // <- not a choice we were expecting

Our enum ensures we are only dealing with the options we expect. If we apply this in our playground, we can display a label that updates as the enum changes value. Our playground file provides the scaffolding for a SwiftUI view that we will update with a custom view to display a TextView whose value is determined by the enum value.

struct Basic: View {
    // let's create a choice and see what happens to us
    //var myImpossibleChoice: AnimalSurvival = .FeedBear
    //var myImpossibleChoice: AnimalSurvival = .FeedWolf
    //var myImpossibleChoice: AnimalSurvival = .FeedSkunk
    var myImpossibleChoice: AnimalSurvival = .PlayDumb
    var body: some View {
        switch myImpossibleChoice {
            
        case .FeedBear:
            Text("Fed the bear, upset the skunk who decided to make a scented statement, and allowed the wolf to eat you with the distraction... No more Teddy")
        case .FeedWolf:
            Text("Fed the wolf, who stealthy got out of there while the bear and skunk attacked you for your poor choice... No more Teddy")
        case .FeedSkunk:
            Text("Fed the skunk, who cleared the air in satisfaction. But seriously, the bear and wolf were so upset the split you in half to share at your stupidity as a way a mutual toward you... No mre Teddy")
        case .PlayDumb:
            Text("Decided to play dumb. For half a milisecond, the bear, the wolf, and the skunk, stared at you in pure disbelief... No more Teddy")
        }
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "waveform.path.ecg.rectangle")
                .resizable()
                .foregroundColor(.red)
                .padding()
                .foregroundStyle(Color.red)
                .frame(width: 100, height: 100)
            
  /          Basic()
                .padding()
            Spacer()
        }
    }
}

Level II - Iterating over enum cases and raw values

The next level of enums is to make listing the options easier and create a concrete representation of the cases. In our example, it would be great to have a String value attached to each enum case to use as the text shown in a list or picker control. Our current enum definition doesn't have a friendly value. Let's update our enum to add custom string values that we will use to display as the label in a picker view. If we adopt the CaseIterable protocol, we can use our enum type to iterate over the options in the enum, allowing us to display our defined string value for each case. Notice that we are telling the Swift compiler that each case in our enum has an attached String value. We are also adding the CaseIterable protocol to allow us to loop over each case efficiently, as well as the Identifiable protocol, because we need to let SwiftUI know how to determine one case from the next.

enum AnimalSurvivalAlt1: String, CaseIterable, Identifiable {
    case FeedBear = "Feed the bear"
    case FeedWolf = "Fead the wolf"
    case FeedSkunk = "Feed the Skunk"
    case PlayDumb = "Play Dumb"
    
    var id: Self { self }
    
    func label() {
        print(self.rawValue)
    }
}

These string values are called the Raw Values for the enum. We access the Raw Value of a case by using the rawValue property of the enum case, as we have done in the label function added to our enum. Enums give us the ability to add functions and computed properties to instances or static functions and computed properties on the enum type, which we can do as if attached to a struct or class.


Let's update our SwiftUI to add a Picker view that provides a list of choices and then displays an appropriate message based on our selection. In SwiftUI, we have to store the state for values we want to exist past the re-rendering of the view, which happens as values that affect the view change. If this is foreign to you, ignore that part and pay attention to how the picker is defined and how the view changes in sync with our choice.

enum AnimalSurvivalAlt1: String, CaseIterable, Identifiable {
    case FeedBear = "Feed the bear"
    case FeedWolf = "Fead the wolf"
    case FeedSkunk = "Feed the Skunk"
    case PlayDumb = "Play Dumb"
    
    var id: Self { self }
    
    func label() {
        print(self.rawValue)
    }
}


struct CaseIterableView: View {
    @State private var badChoice: AnimalSurvivalAlt1 = .PlayDumb
    
    var body: some View {
        VStack {
            Picker("Make a Lunch Choice", selection: $badChoice) {
                ForEach(AnimalSurvivalAlt1.allCases, id: \.self) { choice in
                    Text(choice.rawValue)
                }
            }.pickerStyle(.automatic)

            switch badChoice {
            case .FeedBear:
                Text("Fed the bear, upset the skunk who decided to make a scented statement, and allowed the wolf to eat you with the distraction... No more Teddy")
            case .FeedWolf:
                Text("Fed the wolf, who stealthy got out of there while the bear and skunk attacked you for your poor choice... No more Teddy")
            case .FeedSkunk:
                Text("Fed the skunk, who cleared the air in satisfaction. But seriously, the bear and wolf were so upset the split you in half to share at your stupidity as a way a mutual toward you... No mre Teddy")
            case .PlayDumb:
                Text("Decided to play dumb. For half a milisecond, the bear, the wolf, and the skunk, stared at you in pure disbelief... No more Teddy")
            }
        }
    }
}

We kept our Switch statement and added a way to dynamically change our choice, which controls the label displayed in our view. Notice that we get an array back by calling the allCases property, which Swift adds to our enum by adopting the CaseIterable protocol. We also use the rawValue to display a friendlier label to the user to make a choice.


Level III - Associated Values

What we have is good and probably works in most of the cases you use on a daily basis. However, we have the string value hardcoded when defining the enum. What happens when we want to provide different outcomes for one or more cases when we create instances of the enum? The current implementation of our enum doesn't give us this option. Swift allows us to store other types alongside the case values. The additional information we store is considered an associated value. The nice part about having an associated value is that it retains the type safety and readability of our code and allows us to define the additional information (associated value) when we create an enum instance. Let's update our enum to associate a string value, which we can use to define outcomes of our choices versus setting them when we define the enum.

enum AnimalSurvivalAssociatedValue {
    case FeedBear(String)
    case FeedWolf(String)
    case FeedSkunk(String)
    case PlayDumb(String)
}

In our case, we have separated the enum definition from the String message we want to display. Revisiting our SwiftUI view, let's update this to make our choice random by using a button to trigger. I've added two additional things that you don't have to understand but which work for our example. The first is adding a static computed property to return an array that initializes an instance of each of our enum options with a string message. You can create new instances of any of the enum cases with new string values at any time, which means we aren't tied to decisions a definition time. The second addition is a static function to return a random enum instance from the computed property BadChoices.


We update our view by adding a button with an action to retrieve a random entry in our bad choices computed property. Again, we use our switch statement to display a label that matches our choice. However, a more straightforward switch statement gets the associated string by defining a variable to access the value stored with a matching case. We use this value as the text shown on our label. We get a new value each time we press our button.

extension AnimalSurvivalAssociatedValue {
    static var BadChoices: [AnimalSurvivalAssociatedValue] {
        return [
            AnimalSurvivalAssociatedValue.FeedBear( "Fed the bear, upset the skunk who decided to make a scented statement, and allowed the wolf to eat you with the distraction... No more Teddy"),
            AnimalSurvivalAssociatedValue.FeedWolf("Fed the wolf, who stealthy got out of there while the bear and skunk attacked you for your poor choice... No more Teddy"),
            AnimalSurvivalAssociatedValue.FeedSkunk("Fed the skunk, who cleared the air in satisfaction. But seriously, the bear and wolf were so upset the split you in half to share at your stupidity as a way a mutual toward you... No mre Teddy"),
            AnimalSurvivalAssociatedValue.PlayDumb("Decided to play dumb. For half a milisecond, the bear, the wolf, and the skunk, stared at you in pure disbelief... No more Teddy")
        ]
    }
    
    static func randomChoice() -> AnimalSurvivalAssociatedValue {
        let choice = Int.random(in: 0..<4)
        return AnimalSurvivalAssociatedValue.BadChoices[choice]
    }
}


struct AssociatedValueView: View {
    @State private var badChoice: AnimalSurvivalAssociatedValue = AnimalSurvivalAssociatedValue.randomChoice()

    var body: some View {
        VStack {
            Button(action: {
                badChoice = AnimalSurvivalAssociatedValue.randomChoice()
            }, label: { Text("Get Random Choice")})
            .padding()
            switch badChoice {
            case .FeedBear(let result):
                Text(result)
            case .FeedWolf(let result):
                Text(result)
            case .FeedSkunk(let result):
                Text(result)
            case .PlayDumb(let result):
                Text(result)
            }
        }
    }
}

Associated values on an enum are a superpower that lets you create unlimited options with some creativity and planning. Be careful not to overuse enums with associated values because you know about them. It's still important to use the right tool for the job.


Final Thoughts

This post looked at primary enum usage, adding Raw Values and Case Iteration, and using associated values. As mentioned, I will link the Playground file here for reference. You can learn more about enums here.


Congratulations if you made it this far, and thank you for reading. To reward you for reading, let's revisit the story and hear the ending, or possibly Teddy's.

Teddy had a choice, and he needed to make it quickly. Acting quickly on his feet, he thought of a way to turn the tables in a way that might let him survive. Teddy took his lunch, placed it down, and slowly took a few very important steps back. He then announced, "Bon Appetit, lunch is served. May the best animal win!"


What happened next was unpredictable but entertaining as Teddy watched the animals circling their prize, eyeing each other to determine who would first blink. Although the bear was the largest, he decided not to play this game because he felt it was beneath him. The wolf seized on the opportunity and grabbed the lunch while keeping Teddy in view just in case there might be a trap. When he turned to dash off, he bumped the skunk. The skunk, startled and angry at having lost out on lunch, made an about-face turn and aimed his deadly weapon at the wolf. He showered the wolf with enough skunk ammunition to take the wolf out, who dropped the lunch, dazed, confused, covered, and stumbled into anything and everything in front of him as he did his best to seek and avoid a round two from the skunk. Teddy, realizing this was his opportunity for escape, took off in the other direction, narrowly escaping the change in air freshness around him.


When the air cleared, no animals were left, leaving just a thoroughly contaminated sandwich. Teddy made it out, entertained and in disbelief at his good fortune and the events with the animals. He learned an important lesson. When your options don't look good, add a new case and some associated values in your favor.

If you found this post helpful, please consider liking and following to support our community. With over 20 years in tech, from developer to CTO, I've navigated numerous startup challenges and understand the hurdles you may face. I'm here to offer real, actionable solutions if you're a startup founder, solopreneur, or developer seeking guidance.


Visit keithelliott.co and connect for a free, no-strings-attached 30-minute chat—I'm eager to help you tackle your challenges!

19 views0 comments

Comments


bottom of page