Adding Services to Grocery for Mac with Catalyst and AppKit

The two things I was most excited about while sitting in the audience for the WWDC 2019 keynote were the hotly anticipated encore for the Mac Pro tower computer, and the hotly debated introduction of the Catalyst framework for building Mac apps with UIKit. As a life long Mac user, and previous owner of earlier Mac towers, the introduction of the newest (and perhaps final) giant tower Mac was super exciting. But in some ways, the introduction of Catalyst was more meaningful to me personally as an engineer. Even though I've used a Mac for my entire life, and been programming since high school, I had never built an app for the Mac before.

So when I set out to build a version of my app Grocery for the Mac I knew from the beginning that I wanted it to be as Mac-like as possible. That meant menu bar menus full of keyboard actions, arrow key navigation for lists, right click context menus, expected features like open recent files, and adopting the Mac look and feel with translucent sidebars, a native Mac layout, and supporting system Dark mode and accent colors.

Honestly, getting Grocery to work the way I wanted it to with Catalyst was a lot easier than I thought it would be. Almost all of the work necessary was work I already had to do to build a great iPad app. Everything past that point, like supporting the menu bar, translucent sidebar, accent colors, dock actions, file open menus, etc. fell into place one piece at a time in a fairly orderly fashion.

But this isn't actually an article about Catalyst itself. Rather, this is an article about the final step in the process to Mac-ass the hell out of Grocery by publishing actions to the Mac services menu.

That step started when I re-read an excellent article by Steven Troughton-Smith called Beyond the Checkbox with Catalyst and AppKit. I had read the article over a year ago, and in hindsight it's incredible to me that Steven wrote this literally the first week that Catalyst was announced. Despite reading it a few days after he wrote it, I really needed to go through the process of getting my app ready for the Mac before I was able to really appreciate the additional power it contains. Here's what it let's you do.

What Steven describes in that article is how developers can create an AppKit plugin for their UIKit app running on the Mac. Normally, the UIKit part of a Catalyst app has no access to AppKit and thus can only interact with the parts of the Mac that Apple exposes through the Catalyst layer. But sitting behind all of that is AppKit itself, and the plugin system is the key to unlocking it.

The process of creating a plugin has a few hoops to jump through. The plugin is a bundle that is built and embedded in the target app at compile time. The bundle contains essentially whatever code you want it to. I called my bundle "Catalyst Bundle" and called its primary class "Catalyst App".

class CatalystApp : NSObject, CatalystDelegate {
    weak var callback : CatalystCallback?
}

The next step of the process is to actually load the bundle in your iOS app so that you can use it to talk to your plugin. Our bundle knows the name of its principal class, and also knows some information about its type (thanks to the Objective-C runtime). In my case, my principal class is a subclass of NSObject that conforms to an Objective-C protocol called "Catalyst Delegate". That way, I can create an instance of my principal class and cast it as a Catalyst Delegate. By storing a reference to that object in my app, I can treat it as a delegate to access functionality provided by the plugin.

class CatalystBridge {
    static func loadDelegate() -> CatalystDelegate? {
        #if targetEnvironment(macCatalyst)
        guard let url = Bundle.main.builtInPlugInsURL?.appendingPathComponent("Catalyst Bundle.bundle") else {
            return nil
        }
        guard let bundle = Bundle(path: url.path) else { return nil }
        guard bundle.load() else { return nil }
        guard let catalystBundle = bundle.principalClass as? NSObject.Type else { return nil }
        guard let delegate = catalystBundle.init() as? CatalystDelegate else { return nil }
        return delegate
        #else
        return nil
        #endif
    }
}

One other note about the bundle and protocol. I tried creating a Swift protocol of various forms (conforming to Class, NSObjectProtocol, etc.) but I was not able to get any of those protocols to work like this. Therefore, I resorted to declaring the protocol in Objective-C and including it in an Objective-C Bridging Header in both the plugin and my app target.

@protocol CatalystDelegate <NSObject>

- (void)setCallback:(nullable id<CatalystCallback>)callback;
- (void)setProxyIconFileURL:(nullable NSURL *)url;
- (void)setDockTileImageURL:(nullable NSURL *)url;

@end

Steven's article talks about all of the crazy things that an AppKit plugin like this lets you do. The first ones I tried involved overriding NSApplicationDelegate lifecycle methods. I wanted my app to quit after you closed the window: there's a delegate method for that. I experimented with changing the Dock tile, adding Dock menu actions, and adding a proxy icon in the title bar for the open Recipe file. I'm not shipping any of those changes yet because I didn't get everything to work the way I wanted to, but I am shipping the last thing that I tried which is publishing Services actions to the Services menu.

Services have been around on macOS X since the beginning, or close to it, but I only started using them personally fairly recently. Services are actions that operate on a type of content. The most common content types are text, images, files, and website URLs. I've created several services that operate on images and files as productivity tools. One of my favorites is a service to convert HEIC images into JPEGs (which should probably be built into the system at this point). I've got a few others for dealing with common types of files I use at work. When I saw Steven's mention of Services at the end of his article I knew I had to at least try it so I jumped in.

Trying to find documentation about how to define a Service action is virtually impossible. The most common search results all pertain to what we think of as "services" today, like iCloud or Apple Fitness+. The next most common search results are for user-facing ways to use Services, like Macworld and iMore articles for power users. The Apple tutorial for Services and this 2017 article by Kelvin Ng were the best documentation that I found but they still weren't enough to get everything working correctly for me. I'll try and fill in some of the missing pieces below.

A service definition starts with an entry in the Plist file. This is certainly where the bulk of Apple's documentation focuses, and most of it is pretty helpful. Under the NSServices key there's an array of service dictionaries. The most important keys in each service are the NSMenuItem, which defines the title of the service, the NSMessage, which defines signature of the method that will be called to activate the service, and the NSPortName, which is just the name of the app.

The other 3 keys that I made use of are the NSRequiredContext, NSSendTypes, and NSReturnTypes. The first lesson I learned is that NSRequiredContext needs to be defined with an empty dictionary before the service will appear at all - so that's step one. NSReturnTypes is only required if you plan on building a service that modifies data and sends it back, so if your service is only used for sending data from another app into yours then you can ignore that one.

<dict>
    <key>NSMenuItem</key>
    <dict>
        <key>default</key>
        <string>Add Items to Grocery List</string>
    </dict>
    <key>NSMessage</key>
    <string>addToList</string>
    <key>NSPortName</key>
    <string>Grocery</string>
    <key>NSRequiredContext</key>
    <dict/>
    <key>NSSendTypes</key>
    <array>
        <string>NSStringPboardType</string>
    </array>
</dict>

NSSendTypes was the trickiest for me to figure out. For text based services you only need to put NSStringPboardType in here. But I had trouble finding the right send types for a service I wanted to make that operated on files. I tried a few things that didn't work, but in the end I ended up checking the Info.plist file for BBEdit to see how they defined their "Open File in BBEdit" Service. They provide both the public.file-url and public.utf8-plain-text send types, which is what I ended up using for my Service (and so far is working great).

<dict>
    <key>NSMenuItem</key>
    <dict>
        <key>default</key>
        <string>Send Recipe to Grocery</string>
    </dict>
    <key>NSMessage</key>
    <string>importRecipe</string>
    <key>NSPortName</key>
    <string>Grocery</string>
    <key>NSSendTypes</key>
    <array>
        <string>public.utf8-plain-text</string>
        <string>public.file-url</string>
    </array>
    <key>NSRequiredContext</key>
    <dict>
        <key>NSTextContent</key>
        <array>
            <string>FilePath</string>
        </array>
    </dict>
</dict>

Back in my Catalyst App plugin class, I have a function that gets called after the app starts up that registers itself as the NSApplication's servicesProvider. By registering as the services provider I'm telling the system what class to send messages to when a service is activated.

extension CatalystApp {
    func setCallback(_ callback: CatalystCallback?) {
        self.callback = callback

        NSApplication.shared.servicesProvider = callback == nil ? nil : self

        NSUpdateDynamicServices()
    }
}

That also means that I need to implement methods on my Catalyst App class for each of the services that I defined in my Info.plist file. The method is named with the value I provided in the NSMessage key of my Info.plist file, and is passed 3 parameters: an NSPasteboard, a userData string, and a pointer to an error that can be displayed in the console if something goes wrong.

Since these have to be Objective-C compatible methods for the services system to send messages to them, I had a few hoops to jump through to implement them in Swift. Here's an example of what one of them looks like for the "addToList" Service that adds the selected text content to the grocery list.

@objc func addToList(_ pboard: NSPasteboard, userData: String, error: NSErrorPointer) {
        if let string = pboard.string(forType: .string) {
            callback?.didReceiveAddItems(toList: string)
        }
    }

Getting data out of the pasteboard and putting it back into the pasteboard is pretty much the same as it is in UIKit. The only two issues I ran into here were that the string representation of a fileURL needs to actually be converted into a real URL using the URL(string:) initializer before you can read it. Otherwise it's not useful.

@objc func importRecipe(_ pboard: NSPasteboard, userData: String, error: NSErrorPointer) {
        if let path = pboard.string(forType: .fileURL) {
            callback?.didReceiveAddRecipe(toGrocery: path)
        }
    }

The other issue was that adding items to the clipboard using setString: didn't actually do anything. I had to call clearContents() on the pasteboard first and then use the writeObjects method to add content back before it would actually replace my selected text in another app.

@objc func cleanUp(_ pboard: NSPasteboard, userData: String, error: NSErrorPointer) {
        if let string = pboard.string(forType: .string) {
            let returnText = callback?.didReceiveCleanUpRequest(with: string) ?? string

            pboard.clearContents()
            pboard.writeObjects([returnText as NSString])
        }
    }

The final piece of the puzzle for me was creating another delegate protocol for my plugin to communicate back to my iOS app to tell it when the Service received a new request. I created this as another simple Objective-C protocol to share with the same header file that I used earlier, and made my iOS app's app delegate conform to it. Now my plugin can send action requests and lifecycle events back up to my iOS app that it can pass through the appropriate methods that it already has access to. That works out nicely because all of the Services I created already map closely to NSUserActivity-based Shortcuts that my app supports, so the necessary code to handle them already exists.

@protocol CatalystCallback <NSObject>

- (void)didReceiveAddItemsToList:(nonnull NSString *)text;
- (void)didReceiveAddRecipeToGrocery:(nonnull NSString *)path;
- (void)didReceiveCreateRecipeWith:(nonnull NSString *)text;
- (nonnull NSString *)didReceiveCleanUpRequestWith:(nonnull NSString *)text;

@end

The end result for Grocery is 4 different Services that provide access to some of the most important features of the app:

  • Add Items to Grocery List - add selected text separated by newlines to the current grocery list
  • Clean Up Items using Grocery - modify the selected text using Grocery's CleanUp feature to prettify text
  • New Recipe with Selection - start a new recipe with the selected text
  • Send Recipe to Grocery - import a plain text file containing a Recipe into Grocery
Screen Shot 2021-02-24 at 7.58.18 PM.png

Unmodified Text

Screen Shot 2021-02-24 at 7.58.39 PM.png

Selected Service

Screen Shot 2021-02-24 at 7.58.46 PM.png

Cleaned Up Text

I'm really happy with how these Services turned out. I doubt this is a feature that many users would have asked for, but for me that wasn't the point of trying to build it. I just wanted to prove to myself that it was possible to build something supremely Mac-like in my Catalyst-based app, and I'm very happy to report that it is. And even more importantly to me, this brief little forray into AppKit has me even more excited about continuing to add more Mac-ness to Grocery in the future so that it will continue to be as Mac-assed as it possibly can be.

Saving Previous States with Indirect Enums in Swift

I recently learned a new trick for how to structure enums for switching between states in my app Grocery. I’ve written a lot about how to use enums to manage state in an app so it’s always fun to learn new tricks with enums - especially one like this that I think I’ll be using a lot.

In my app there are several screens that switch between a view mode and an edit mode. In a simple enum those states might be represented simply as view and edit. When the user finishes editing, the time comes to switch the view back into view mode, and so we change our state variable back to the .view case.

enum RecipeParsingState {
    case view(text : String)
    case edit
}

However, things get more complicated if the screen supports more than one type of view state. It becomes difficult to know which case the user needs to return to after editing because that’s no longer inherently defined in the enum structure.

One such example is a text input screen that I’m using in a new recipe importing share extension in Grocery. The screen can be used for selecting a description, ingredients, or instructions from a recipe. Each of those types are cases that the state enum for the screen can be in, since the screen appearance needs to be customized for each of them.

enum RecipeParsingState {
    case description(text : String)
    case ingredients(text : String)
    case instructions(text : String)
    case edit
}

I really only need one edit case for the screen because the editing behavior is the same for all of them. But once I’ve put my screen’s state in the edit case I need a way to figure out which case to return to when the user finishes editing.

I could add another variable to the screen and store the previousState as an instance variable. But this is messy for all of the same reasons that removing Boolean instance variables is important to do in State Driven Development. Wherever possible, it’s better to encapsulate instance state within the context of where it’s actually used.

var previousState : RecipeParsingState?

The solution I ended up with is to add the previous case as an associated value on the edit enum case. That way, when editing is done, we can consult the associated value and return our screen’s state back to the previous case and continue importing the recipe.

indirect enum RecipeParsingState {
    case description(text : String)
    case ingredients(text : String)
    case instructions(text : String)
    case edit(returnToState : RecipeParsingState) 
}

Because the enum is now referencing itself, it needs to become an indirect enum. I hadn’t actually heard of indirect enums before, and the Swift compiler helpfully told me I needed to add the indirect keyword after I included the enum type as an associated value. I did a little bit of research and I haven’t seen any downsides to this structure yet, and the enum seems to behave the same way other enums would except with this new allowance that it can reference itself.

There’s a few screens I can go back to refactor now with this new approach which should simplify how I was managing the states of those screens too. Hopefully this little trick helps in one of your projects too.

On Apple's SwiftUI Header File Documentation

I wanted to add my thoughts to Casey’s post about Apple’s developer documentation which itself stems from a really great episode of Under the Radar. I listened to the same episode and it helped crystallize the friction that I’ve been feeling with the process of learning to use SwiftUI in my work.

When I started learning to build iOS apps in 2010 I learned by reading tutorials and watching videos. At first the only things I knew how to do with a UITableView were the things I read about in tutorials. If I ran into a problem I didn’t know how to solve, I google searched until I found a tutorial that mentioned the name of the delegate method I needed to implement.

Eventually I learned enough of the patterns that iOS frameworks were built around that I realized I could probably just find the answer to my question in the built in documentation and header files. There were some great tools like Ingredients and Dash that helped me find what I wanted, but more often than not I relied on header files to solve my problem.

The beauty of that approach is that you’re always one command click / jump to definition away from discovering an answer. If you see the name of the type you need to learn about, or the signature of a method you have a question about, you can just click on it and start your research. You can see everything that type or method can do laid out in the header file.

I used that approach to learn the Compositional Layout system that Casey mentions. When I wanted to figure out how to make a sticky header in a scrolling list, I could command click through the definitions of a few types until the pinToVisibleBounds property of the NSCollectionLayoutBoundarySupplementaryItem class caught my eye. The answer was there for me to find after spending a few clicks to find it.

That brings us to today and learning to use SwiftUI. The Swift programming language may have gotten rid of the compiler’s need to define a real header file but it didn’t get rid of the programmer’s need to have clear and orderly descriptions of types and what they can do. When I started experimenting with SwiftUI my instinct was to start command clicking on types to learn from their definitions. This time though, command clicking didn't help me. When you jump to the definition of a type in SwiftUI you end up in the 22,000 line definition of SwiftUI itself.

Screen Shot 2020-11-10 at 10.38.06 PM.png

You actually can discover some of the modifiers and important types there that you need to use, but the lack of structural organization and the sheer scope of of the definition file keeps the information feeling like it's trying to hide from you.

I have yet to be able to solve any kind of non-trivial SwiftUI problem on my own inside of Xcode. For every issue I run into I’m falling back to the time-tested practice of google searching for a tutorial that happens to mention the name of a modifier that I need to know. The tutorials and new blogs and websites that I have found have been wonderfully helpful, but I don't believe they should be the only resources I can use to solve problems that I run into while using this framework.

I believe now that this aspect of SwiftUI is the most important issue standing in the way of me adopting it into a greater part of my workflow. I’ve now shipped quite a bit of SwiftUI code in the apps I work on, including in Widgets for iOS 14, but this friction in the process of learning SwiftUI takes away from the experience and doesn’t make me excited to learn more about it. It leaves me feeling like the incredible power of SwiftUI is trapped behind an invisible wall banging to be heard, but I just simply can’t see or hear it as I wander about concerned and trying to find it.

State Driven Development - The Beauty of Enums in Swift

State Driven Development

Like many developers, I noticed enums pretty quickly after I started programming in Swift. I had used enums in other programming languages too, but they had never been as exciting to work with and use as enums in Swift. I remember reading this blog post by Casey Liss a few years ago and feeling the exact same excitement enums. Enums in Swift are awesome and fun to use!

The more I use enums in Swift, the more convinced I am that enums are so valuable that they're worth designing software around. We have terms like object oriented programming and protocol oriented programming for describing how we design software around types. How we design software around enums is just as important and useful. I think of designing software around enums as State Driven Development, and that's what I hope to describe here.

But the story of state driven development doesn't start with enums. We need to start with a different programming tool that we're all even more familiar with. It's something that we probably use every day while writing code.

That of course, is BOOL. Specifically BOOLs used as instance variables that represent the state of an object. It's actually a tool that we use ALL the time. We define these properties on objects we create ourselves, often out of convenience, or we set these properties on other objects that we interact with.

var instanceVariable : Bool


So how often do we use BOOLs? We can use a regular expression to search our Xcode project for this pattern to see what all of the examples look like.

// Regex for boolean state
// Match Bool typed vars that aren't computed properties
var.*:.*Bool(\n|.*=.*)
var.*=\s?(true|false)


In the project find navigator in Xcode, select Regular Expression from the dropdown and enter the pattern you want to match. If your project is anything like mine you'll probably be pretty surprised at what you find! Below are some of the examples I found in my app Grocery.

var active : Bool
var contentLoaded : Bool
var dataLoaded : Bool
var editing : Bool
var enabled : Bool
var expanded : Bool
var handlingTouches : Bool
var hasTakenUserAction : Bool
var isCompleted : Bool
var isRunning : Bool
var lastTouchedButtonSide : Bool
var lastTouchedRightSide : Bool
var notified : Bool
var previewEnded : Bool
var saved : Bool
var started : Bool


Some of these seem fairly clear in terms of what they mean and when they should be true or false. But the problem with BOOLs is that it's not always clear what they mean. How often do their values change and who is responsible for changing them? How do different BOOLs relate to each other? For example: when one BOOL is true, should another BOOL be false?

Starting a workout

Let's go through an example together. We're working on an app for tracking workouts. The app has one purpose: to allow the user to track a workout. When the user opens the app they can always start a workout. Imagine there's just a giant button that says "start workout". So we write a function that starts a workout when the button is tapped.

func startWorkout() {
    // ...
}


This works great, but later on we realize our app can start two workouts if the button is tapped twice. How do we prevent that?

One variable

We add a BOOL to represent whether or not a workout is in progress. Now we can guard against starting a second workout if the first one hasn't finished.

var workoutInProgress : Bool = false

func startWorkout() {
    guard workoutInProgress == false else {
        return
    }

    workoutInProgress = true

    // ...
}


This also works great, for a while. But then we realize there's another requirement missing from this method. If a user hasn't logged into the app yet then we won't be able to save their workout.

Two variables

To prevent that from happening, we add another BOOL to track whether or not the user has logged in. We expect this property to be set to true when the user logs in, and we add another case to our guard statement to make sure it's still true. Now the user has to be logged in to start a workout, and we can only have one workout running at a time.

var hasLoggedIn : Bool = false
var workoutInProgress : Bool = false

func startWorkout() {
    guard hasLoggedIn && workoutInProgress == false else {
        return
    }

    workoutInProgress = true

    // ...
}


This is getting pretty complicated though. First, we need to trust that something will set hasLoggedIn to true. We also need to set workoutInProgress back to false at the end of the workout, or we won't be able to start another workout later. The potential for bugs to creep in has gone up quite a bit just from adding one more variable.

Now we receive another requirement for our start workout method. We want to prevent starting a new workout while we're in the middle of saving the prior workout so we add a new variable to track if we are currently saving a workout.

The tangled web of state

This is where the web of state starts to get very tangled. First we set workoutInProgress equal to false since we've stopped tracking the workout, but saving the workout is an asynchronous task, so we set the savingWorkout variable to true when we start saving, and back to false when we finish saving.

var hasLoggedIn : Bool = false
var workoutInProgress : Bool = false
var savingWorkout : Bool = false

func stopWorkout() {
    workoutInProgress = false
    savingWorkout = true

    saveWorkout { (success) in
        self.savingWorkout = false
    }
}


Back in our start workout method, we need to add a third case to our guard. The user still has to be logged in, and they can't start a workout if there is a workout still in progress or being saved.

var hasLoggedIn : Bool = false
var workoutInProgress : Bool = false
var savingWorkout : Bool = false

func startWorkout() {
    guard hasLoggedIn && workoutInProgress == false && !savingWorkout else {
        return
    }

    workoutInProgress = true

    // ...
}


But that's turning into a really long guard statement. There's a lot that we need to remember to do as programmers to make sure that all of these state variables operate the way that this function expects them to.

Valid and invalid states

So here's what's crazy about this. We have 3 BOOL values, but by combining them together we can represent 8 completely distinct states, even though only 4 of them are actually valid states for our application to be in.

var hasLoggedIn : Bool
var workoutInProgress : Bool
var savingWorkout : Bool

These are the four combinations of values that are actually valid. They follow the user's expected path through the application. The user logs in, starts a workout, and saves the workout.

  • User Not Logged In, No Workout, Not Saving
  • User Logged In, No Workout, Not Saving
  • User Logged In, Workout In Progress, Workout Not Saving
  • User Logged In, No Workout, Workout Is Saving


But with BOOLs we can represent combinations of values that shouldn't be possible. These four combinations represent potential bugs. They are states we could accidentally find ourselves in if we fail to update our BOOLs correctly. Something like the user not being logged in, but still having both a workout in progress and another workout being saved.

  • User Not Logged In, Workout In Progress, Not Saving
  • User Not Logged In, Workout In Progress, Workout Is Saving
  • User Not Logged In, No Workout, Workout Is Saving
  • User Logged In, Workout In Progress, Workout Is Saving


This is the perfect case for creating an enum. We have four valid states that we've identified, and we know that our object should only ever be in one of these states. We need to construct an enum that shows exactly which of those 4 states our object is in.

These are our four valid states:

  • Hasn't logged into the app yet
  • In the middle of a workout
  • Saving the last workout
  • Idle


And here's what they look like as an enum:

enum WorkoutApplicationLifecycle {
    case notLoggedIn
    case workoutInProgress
    case savingLastWorkout
    case idle
}


By using an enum instead of multiple BOOL values we've actually made the invalid states impossible to represent in code. That's going to make an entire class of bugs impossible to write, because we can't cause our app to be in a state that can't be represented.

But our job isn't done yet. Even though we've created an enum to accurately describe the state of our object, we still have to update our code to ask specific questions using the enum we’ve defined.

Asking specific questions

The question we're trying to ask in our guard statement is: "Can I start a workout? 🤔" But that's not what the code here is asking yet. The question it's asking is actually pretty vague. These three variables are describing the state of the object. They're intended to answer questions like whether or not the user has logged in. They weren't designed to answer questions about starting workouts.

var hasLoggedIn : Bool = false
var workoutInProgress : Bool = false
var savingWorkout : Bool = false

func startWorkout() {
    guard hasLoggedIn && workoutInProgress == false && !savingWorkout else {
        return
    }

    workoutInProgress = true

    // ...
}


We can improve the code by referencing our lifecycle enum. The idle case happens to be the only case where we can start a workout, so technically this code will work. But we're still comparing two values to see if they’re equal instead of asking a specific question about starting a workout.

var lifecycle : WorkoutApplicationLifecycle = .notLoggedIn

func startWorkout() {
    guard lifecycle == .idle else {
        return
    }

    workoutInProgress = .workoutInProgress

    // ...
}

Extending enums with variables!

Fortunately, Swift gives us a great way to ask more specific questions and implement custom behavior with our enums. We can extend our lifecycle enum with a variable that answers the question: can we start a workout? We know that the app should only start a workout in the idle case, so we return true there and return false for the other cases.

enum WorkoutApplicationLifecycle {
    case notLoggedIn
    case workoutInProgress
    case savingLastWorkout
    case idle

    var canStartWorkout : Bool {
        get {
            switch self {
            case .notLoggedIn, .workoutInProgress, .savingLastWorkout:
                return false
            case .idle:
                return true
            }
        }
    }
}


Here's what using our new canStartWorkout extension looks like in the guard statement.

var lifecycle : WorkoutApplicationLifecycle = .notLoggedIn

func startWorkout() {
    guard lifecycle.canStartWorkout else {
        return
    }

    lifecycle = .workoutInProgress

    // ...
}


Our stopWorkout method also sets our lifecycle correctly to indicate when the app is saving a workout and when the app is back to being idle.

var lifecycle : WorkoutApplicationLifecycle = .notLoggedIn

func stopWorkout() {
    lifecycle = .savingLastWorkout

    saveWorkout { (success) in
        self.lifecycle = .idle
    }
}


This way, our enum cases can cleanly represent state, and our enum extensions can clearly describe the questions that our enum is able to help answer.

Avoiding direct case comparison


Here's another example of why it's best to avoid direct case comparison with enums and implement extensions to answer specific questions. Consider a collection of possible activity types. We have an ActivityType enum that enumerates running, walking, and yoga activities.

enum ActivityType {
    case running
    case walking
    case yoga
}


There are plenty of questions that the activity type can help us answer. One of them is whether or not to start the GPS system. Let's say that we only want to use GPS for running workouts. We check if our activity type is equal to running, and turn GPS on or off.

if activityType == .running {
    gpsProvider.start()
}

if activityType == .running {
    gpsProvider.stop()
}


Eventually we decide to use GPS for walking workouts too, so we update our first if statement to support walking. But we forgot to update the second if statement, and now our app has a bug because we won't turn GPS off when the user stops walking.

if activityType == .running || 
   activityType == .walking {

    gpsProvider.start()
}

if activityType == .running {

    gpsProvider.stop()
}


When we check for equality between two enum cases, we lose the opportunity to create a single source of truth for state driven behaviors. Let's say we wanted a hiking activity to support GPS as well. Wouldn't it be nice to have one place to make that change?

The question we're trying to ask is whether or not our activity type supports GPS. We can extend our enum with a variable to answer that question, using its own cases to customize the behavior. Whenever we're ready to add hiking, we'll just have one change to support GPS.

enum ActivityType {
    case running
    case walking
    case yoga

    var supportsGPS : Bool {
        switch self {
        case .running, .walking:
            return true
        case .yoga:
            return false
        }
    }
}

if activityType.supportsGPS {
    gpsProvider.start()
}


It's important to consider how enum extensions can help us describe custom behavior and answer specific questions about the state of our objects. Once we start thinking about specific questions, we can start extending our enums to help provide specific answers.

There are lots of questions we might want to ask our enums that aren't inherently answered by the names of specific cases. Nothing about the name of an activity type describes if it should support GPS. Adding an extension for that specific question is how we describe that custom behavior.

The best rule of thumb here is that any time that a program needs to use state to answer a question, there should be enum extension to help answer it.

Extend enums with functions!

We can also extend enums with functions so that we can take different actions depending on the state of the object.

Back in our original example, we've defined an action taken method on the enum that accepts a reference to the view controller. The enum can then take advantage of the view controller’s public API without having to know anything about its internal structure. The view controller exposes start, stop, and save methods that our enum can use.

enum WorkoutApplicationLifecycle {

    // ...

    func actionTaken(from viewController : WorkoutViewController) {
        switch self {
        case .notLoggedIn:
            viewController.login()
        case .workoutInProgress:
            viewController.stopWorkout()
            viewController.saveWorkout()
        case .savingLastWorkout:
            break
        case .idle:
            viewController.startWorkout()
        }
    }
}


What's nice about this is that the logic for what to do when the button is pressed is entirely encapsulated in the thing that describes the possible states of the button. All we have to do when we want to change behavior is change the options described by that enum case.

It may seem strange to literally pass something like a view controller to a function on an enum. Should the enum actually have access to something that substantial? I like this pattern for a few reasons. First, the dependency is explicitly created because the view controller is passed as an argument to the function, rather than implicitly accessed as a property. Second, the enum can't hold onto the view controller, so it can only use it's API inside of this specific function. It ends up being a safer way to reuse code than many other types of less-specific dependencies.

Our view controller benefits from this encapsulation too with a very clean IBAction implementation.

@IBAction func didTapActionButton() {
    lifecycle.actionTaken(from: self)
}


Describing custom behavior

We can keep extending our enum to describe more behaviors. We can ask the state for the answers to questions like what the title of an action button is, or what background color to use.

enum WorkoutApplicationLifecycle {
    case notLoggedIn
    case workoutInProgress
    case savingLastWorkout
    case idle

    var canStartWorkout : Bool
    var actionTitle : String
    var backgroundColor : UIColor
    var barButtonItems : [UIBarButtonItem]
}


In this example, the enum knows what the state of our lifecycle is, so it can return the correct action title for each state.

enum WorkoutApplicationLifecycle {
    var actionTitle : String {
        get {
            switch self {
            case .notLoggedIn:
                return "Sign In"
            case .workoutInProgress:
                return "End Workout"
            case .idle:
                return "Start Workout"
            default:
                return ""
            }
        }
    }
}


Note: It's important to be careful using default switch statements, because the compiler won't warn us when new cases are added to our enum. I typically avoid using it unless absolutely necessary, even when only one case needs to return a different value.

We can also return different background colors for different states.

enum WorkoutApplicationLifecycle {
    var backgroundColor : UIColor {
        get {
            switch self {
            case .notLoggedIn:
                return UIColor.blue
            case .workoutInProgress: fallthrough
            case .savingLastWorkout:
                return UIColor.red
            case .idle:
                return UIColor.green
            }
        }
    }
}


Note: Some switch statements benefit from using the fallthrough syntax to return the same values for multiple cases. Fallthrough requires the explicit use of the fallthrough keyword as opposed to implicit implementations in other languages like Objective-C. So we have to opt-in to using fallthrough, but I like this option and I've started using it more often.

The temptation of BOOL

Every time we add a state, it's tempting to use a BOOL instead of an enum. They're easy to create and simple to use.

var workoutInProgress : Bool = false

func startWorkout() {
    guard workoutInProgress == false else {
        return
    }

    // ...
}


But even from just the first BOOL we defined, it would have been better to use an enum. The purpose of the workoutInProgress state variable is different from the guard statement's question of whether or not it can start a workout. Having the enum gives us an obvious extension point when the states and behavior of the object need to change in the future.

As I've been exploring State Driven Development, I've been trying to always describe state with an enum instead of a BOOL. But sometimes I do still use a BOOL just to see how it goes.

var trap : Bool


Every time I've gone ahead with creating a BOOL I've gone back and replaced it with an enum. Enums are just a better tool for being specific about state, and being able to answer a broad range of questions that depend on state. It's become a foundational part of how I like to write software.

When we set the value of an enum, we're telling the object what state it's in. Our lifecycle enum is describing the states the object can be in. It's telling us that we're in the workout recording state, or the saving a workout state, or the not logged in state, or the idle state.

Any time that a program enters a state, there should be an enum defined with a case to describe it.

Adding new states

The real ah-ha moment for State Driven Development comes when we need to add a new case to our enum. We need to add a new state to support restoring a workout.

enum WorkoutApplicationLifecycle {
    case restoringWorkout
}


After adding the new case, our switch statements prompt us to implement it in all of our extension variables and functions.

enum WorkoutApplicationLifecycle {
    var backgroundColor : UIColor {
        get {
            switch self {
            case .notLoggedIn:
                return UIColor.blue
            case .workoutInProgress: fallthrough
            case .savingLastWorkout:
                return UIColor.red
            case .idle:
                return UIColor.green
            case .restoringWorkout:
                  // return ?
            }
        }
    }
}


This turns our enum extensions into the public API for extending the behavior of our object! As long as we avoid direct case comparisons and the default switch statement, the compiler will tell us exactly what we need to change to support the new case. That allows us to answer each of the specific questions below:

  • Can start workout? - false
  • Action title? - default
  • Background color? - UIColor.red
  • Action taken? - break

This ends up being a much faster and safer way to extend behavior to support new states. Since we have a single source of truth for making decisions that involve state, we don't need to remember where various BOOLs or enum cases were used throughout the code base and check to see if they require updates to handle the new state.

Associated values

Now that we've covered the basics of State Driven Development, I want to focus on a few features of enums in Swift and how they make State Driven Development even better. Associated Values are my favorite Swift enum feature! An associated value is basically a tuple that we can add to any enum case. We can access the values stored in that case's tuple whenever we need to.

Here's what the syntax looks like for specifying that an enum case has an associated value. The enum case is workoutInProgress, and current is the parameter name of a Workout object.

enum WorkoutApplicationLifecycle {
    // ...
    case workoutInProgress(current : Workout)
}


When we want to get the associated value out of an enum case we can do it with Swift's pattern matching syntax. Since the value is a constant we have to use let to tell swift to treat it as a constant.

if case .workoutInProgress(let workout) = lifecycle {
    // ...
}


Slightly confusingly, all of these are actually valid syntax for obtaining an associated value from an enum case. The call-site name can be used or ignored, and let can be after the case or inside the parentheses. When we use let after case then it applies to all of the associated values within the tuple, which is actually pretty handy if you have multiple associated values for one enum case.

if case let .workoutInProgress(current: workout) = lifecycle { }
if case let .workoutInProgress(workout) = lifecycle { }
if case .workoutInProgress(let workout) = lifecycle { }


The associated value can also be ignored if it isn't needed.

if case .workoutInProgress = lifecycle { }


These are the same examples for building switch statements, all of which are valid syntax as well.

switch self {
    case let .workoutInProgress(current: workout):
    case let .workoutInProgress(workout):
    case .workoutInProgress(let workout):
    case .workoutInProgress:
}


This is the syntax that I prefer using. The compiler autocompletes the call-site name "current" for us, so all we have to do is hit enter and type a variable name. I like using let after case so that it applies to all of the associated values.

if case let .workoutInProgress(current: workout) = lifecycle { }

switch self {
    case let .workoutInProgress(current: workout):
}


Here's what setting an associated value looks like. Back on our start workout method, we create the new workout object and pass it along when we set our lifecycle to the workout in progress state.

var lifecycle : WorkoutApplicationLifecycle = .notLoggedIn

func startWorkout() {
    guard lifecycle.canStartWorkout else {
        return
    }

    var newWorkout : Workout = Workout()

    // ...

    lifecycle = .workoutInProgress(current: newWorkout)
}


We can use the associated value to help answer more types of questions by expanding the scope of our enum cases. Here we're using it to return a different title based on our current workout's activity type.

enum WorkoutApplicationLifecycle {
    // ...
    case workoutInProgress(current: Workout)

    var navigationTitle : String {
        switch self {
        case let .workoutInProgress(current: workout):
            return workout.type.activityName
        }
    }
}


Highly relevant values

Associated values are highly relevant to the states of our objects. It makes a lot of sense to have the workout property live within the workoutInProgress case because that's the only state where our object can have an active workout.

We can pass the in progress workout's associated value to our save workout method to specify the workout that needs to be saved.

enum WorkoutApplicationLifecycle {
    // ...
    case workoutInProgress(current: Workout)

    func actionTaken(from viewController : WorkoutViewController) {
        switch self {
        case let .workoutInProgress(current: workout):
            viewController.stopWorkout()
            viewController.saveWorkout(current: workout)
        }
    }
}


Result is another example where having highly relevant values is useful.

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


It makes sense that the Result enum only includes Error as an associated value on the failure case because the error is only relevant when there's been a failure. When we need to handle the failure we have the error available to use within that context. Otherwise we don’t need to worry about it.

Providing useful context

Enum cases with associated values can help provide useful context. In this example, our table view cell has defined a protocol for the types of content it can display. Most of the content properties are optional though, because the cell can support showing an image without text, or text without images.

protocol TableViewCellContent {
    var titleText : String?
    var descriptionText : String?
    var image : UIImage?
    var accessoryType : UITableViewCellAccessoryType
}

// Supported Configuration Options
* Simple cell with one line of text
* Detailed cell with two lines of text
* Image Cell
* Accessory Image Cell


We can create a cell configuration enum with associated values to provide the context for when specific cell content should be used. And since enums can conform to protocols, our configuration enum will implement property getters for all of the variables defined by the cell content protocol and return the associated values specified by each configuration.

protocol TableViewCellContent {
    var titleText : String?
    var descriptionText : String?
    var image : UIImage?
    var accessoryType : UITableViewCellAccessoryType
}

enum CellConfiguration : TableViewCellContent {
    case simpleCell(title : String)
    case detailedCell(title : String, description : String)
    case imageCell(image : UIImage)
    case accessoryCell(title : String, 
                       image : UIImage, 
                   accessory : UITableViewCellAccessoryType)
}


With our cell configuration enum in place, we can define mapping functions on our model objects to return the cell configuration best suited for the data they need to show.

extension Workout {
   var cellConfiguration : CellConfiguration {
        return .detailedCell(title: workoutTitle, 
                description: activityType.description)
    }
}

extension User {
    var cellConfiguration: CellConfiguration {
        return .imageCell(image: profileImage)
    }
}


What's nice about this pattern is that it keeps details of the data models outside of the cell configurations. The part of our app that chooses how to represent a model object is basically reading a menu, and choosing what to order from of it.

"I'll have a detailed cell with a workout title and an activity type description".

Our cellForRowAtIndexPath implementation is a lot simpler too because we don't have to worry about performing any configuration here. When we dequeue our cell we just get our model object for that row, and setup the cell with the enum case for that type of model object.

func tableView(_ tableView: UITableView, 
   cellForItemAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell
        (withReuseIdentifier: reuseIdentifier, 
                          for: indexPath) as! CustomTableViewCell

    let workout = workouts[indexPath.row]

    cell.setup(with: workout.cellConfiguration)

    return cell
}


And because our cell defined a protocol for its content, the cell doesn't need to know anything about the enum that implemented the protocol. It's just using the content that it was provided with.

class CustomTableViewCell : UITableViewCell {

    @IBOutlet weak var titleLabel : UILabel?
    @IBOutlet weak var descriptionLabel : UILabel?
    @IBOutlet weak var imageView : UIImageView?

    func setup(with content : TableViewCellContent) {
        titleLabel?.text = content.titleText
        descriptionLabel?.text = content.descriptionText
        imageView?.image = content.image
        accessoryType = content.accessoryType
    }
}


Single source of truth

Enums can also be the single source of truth for complex objects like view controllers.

There are usually several "modes" that a view controller can be in. The modes might be designed around features like viewing or editing a document, or recording or saving a workout.

A lot of things can change based on the mode. The navigation bar title and bar buttons may be different. There might be an overlay view that's hidden or visible. There might be an activity indicator that's started or stopped. Reasoning about all of these appearance customizations can be a big challenge without a single source of truth that describes what they all do in each possible state.

protocol ViewControllerAppearance {
    var title : String
    var leftBarButtonItems : [UIBarButtonItem]
    var rightBarButtonItems : [UIBarButtonItem]
    var overlayAlpha : CGFloat
    var activityIndicatorActive : Bool
}

enum ViewControllerState : ViewControllerAppearance {
    case .editing(Workout)
    case .saving(Workout)
    case .viewing(Workout)
}


I fixed an issue recently where I was showing the wrong navigation bar button items for the state that the view controller was in. The screen was showing a cancel button instead of a close button because the navigation items where changing separately from where my view controller's state was being changed.

A great way to fix these types of issues is to implement appearance customizations as a function of state.

Appearance as a function of state

We can define a setupView function and pass in the state that we're transitioning to. The function can perform any necessary setup based on the requirements specified by our state enum.

func setupView(for state: ViewControllerState) {
    navigationItem.title = state.title
    navigationItem.leftBarButtonItems = state.leftBarButtonItems
    navigationItem.rightBarButtonItems = state.rightBarButtonItems

    UIView.animate(withDuration: 0.35) {
            self.overlayView.alpha = state.overlayAlpha
        }

    if state.activityIndicatorActive {
        activityIndicator.startAnimating()
    } else {
        activityIndicator.stopAnimating()    
    }
}

Behavior as a function of state

Other behavior is likely to change based on our view controller's state too. We might have different table view cell selection behavior for editing a document versus viewing it. We might also have a completely different data source to show in each state.

protocol ViewControllerBehavior {
    func didSelectCell(at indexPath: IndexPath)
    func highlightCell(at indexPath: IndexPath)
    func handleDragOnCell(at indexPath: IndexPath)
    func numberOfRows(in section: Int) -> Int

    var numberOfSections: Int
    var supportsDragAndDropInteraction: Bool
}

extension ViewControllerState : ViewControllerBehavior {}


At some level, everything about a screen depends on the state it is in. By moving all of this logic into an enum, especially a single enum that describes all of the possible states that the screen needs to represent, we greatly simplify our view controller implementation and keep all of our complex logic contextual to where the states are defined.

Swift special features: CaseIterable

There are of course several other features that make using enums in Swift even easier. It's entirely possible to write an entire app without using some of these features, but when you do need them it really is nice to have them.

One of these features is CaseIterable. If we need to iterate through all of an enum’s cases we can conform to the CaseIterable protocol. When we conform to CaseIterable, Swift automatically implements an allCases property for us and keeps it updated when we add or remove cases.

enum WorkoutApplicationLifecycle : CaseIterable {
    case notLoggedIn
    case workoutInProgress
    case savingLastWorkout
    case idle
}

WorkoutApplicationLifecycle.allCases 

[notLoggedIn, workoutInProgress, savingLastWorkout, idle]


Note: While it is possible to conform to CaseIterable inside of an extension, we do need to conform to the protocol in the same file that our enum is defined in or it won't work.

We can also provide a custom implementation of allCases if we want to, which is important for enums that use associated values. Swift can't know all of the possible associated values, so it will complain unless we provide an implementation.

enum WorkoutApplicationLifecycle {
    case notLoggedIn
    case workoutInProgress(current : Workout)
    case savingLastWorkout
    case idle
}

extension WorkoutApplicationLifecycle : CaseIterable {
    static var allCases: [WorkoutApplicationLifecycle] {
        return [.notLoggedIn, .savingLastWorkout, .idle]
            + WorkoutActivityType.allCases.map(.workoutInProgress)
    }
}


The downside, of course, is we do have to remember to keep this updated if our cases change.

Swift special features: RawRepresentable

Enums in Swift can also have raw values. Traditionally, most enums use Ints as their raw values, but we can use Strings too. This is really useful for defining things like keys that we want to reuse in multiple places.

enum DefaultKeys : String {
    case loggedInUserId
    case selectedActivityType
}

print(DefaultKeys.selectedActivityType.rawValue)

selectedActivityType


Swift can define the raw value as the name of each case automatically, or we can provide our own raw value for each case. Here we're defining a specific string for the selected activity type case, and that's the string we get back when we access the raw value.

enum DefaultKeys : String {
    case loggedInUserId
    case selectedActivityType = "SelectedActivityTypeDefaultKey"
}

print(DefaultKeys.selectedActivityType.rawValue)

SelectedActivityTypeDefaultKey


Support for String and Int raw values is built into Swift, but because Enums can conform to protocols we can actually make an enum work with any raw value by conforming to the RawRepresentable protocol. Here we have an Application Theme enum that defines different color schemes. The enum is defining the options, but there is a separate struct that is actually defining the values that our interface needs to configure itself.

enum ApplicationTheme {
    case defaultLight
    case darkMode
    case blueberry
    case eggplant
    case mustard
    case broccoli
}

struct Theme : Equatable {
    let name : String
    let background : UIColor
    let cell : UIColor
    let tint : UIColor
    let foreground : UIColor
    let statusBar : UIStatusBarStyle
}


How can we return the correct Theme struct for each ApplicationTheme enum case? One option is to define a custom raw value on the ApplicationTheme enum. We conform to RawRepresentable on our ApplicationTheme enum and make the Theme struct its raw value. Once we're done we'll have direct access to the theme struct right from the enum case.

enum ApplicationTheme : RawRepresentable {
    typealias RawValue = Theme

    init?(rawValue: Theme) {
        // ...
    }

    var rawValue: Theme {
        // ...
    }
}

print(ApplicationTheme.broccoli.rawValue.name)

Brocolli


Enums with custom raw values don't support associated values, so we do have to pick and choose which approach makes the most sense for our use case. We could have decided to use associated values to solve this problem by initializing every case with a Theme struct.

enum ApplicationTheme {
    case defaultLight(theme : Theme)
    case darkMode(theme : Theme)
    case blueberry(theme : Theme)
    case eggplant(theme : Theme)
    case mustard(theme : Theme)
    case broccoli(theme : Theme)
}


The problem with using associated values in this example is that we need to know which Theme struct to pass in to create each case. That's hard to define, since the enum case can be initialized with any theme struct and not just the specific one intended for that case. This makes it almost impossible to enforce correctness which in-turn makes the enum harder to use.

Since there are tradeoffs between associated values and custom raw values, I recommend looking at associated values first. If most or all of the associated values are duplicates, then it's a good indication that a custom raw value makes more sense for the use case.

Another benefit of using a custom raw value is that it makes supporting CaseIterable easier, since we don't have any associated values.

enum ApplicationTheme : CaseIterable { 
    case defaultLight
    case darkMode
    case blueberry
    case eggplant
    case mustard
    case broccoli
}

ApplicationTheme.allCases

[.defaultLight, .darkMode, .blueberry, .eggplant, .mustard, .broccoli]


The default implementation of CaseIterable works perfectly for our ApplicationTheme enum and gives us back an ordered list of all our theme options from one variable that the compiler will maintain for us.

🎉 Confetti Moments 🎊

I wanted to end this post with something fun by sharing one of my favorite real-world enums that I've created.

During a recent hackathon project I added easter egg confetti effects to the MapMyRun and MyFitnessPal apps. I used an enum to define the type of confetti we could show, including custom colors, images, or even emoji.

public enum ConfettiMoment {
    // Color Confetti
    case confettiMMF
    case confettiMFP
    case confetti🏳️‍🌈

    // Emoji Confetti
    case 🌮

    // Custom Confetti
    case customColors(colors : [UIColor], name : String)
    case customImages(images : [UIImage], name : String)

    func effectScene() -> SKScene
}

We have some colors and emoji confetti moments pre-specified, and two additional cases for specifying custom colors and images. Each case just needs to return an effect scene for creating the confetti. The confetti can appear when someone completes their diary, congratulates someone on a great workout, or mentions tacos in a workout post on Taco Tuesday.


Try it!

I've loved hearing from other developers who, after hearing about State Driven Development, have gone back to try it in their projects. I've talked to many people who have used enums as a way to simplify complex states within their applications and be more specific about answering questions as a function of state.

If you want to try State Driven Development in your project, try creating an enum to describe state instead of a BOOL. I like this approach to getting started because creating an enum instead of a BOOL is easy to remember. Once you've done that, try defining an extension to answer a question as a function of state.

I've adapted this post from two different conference talks where I've spoken about State Driven Development and enums in Swift. I loved putting those talks together and sharing them, which is what motivated me to create a written account of this content and share it here. If you want to see the original talks check out the links below.

Additional resources

If you want keep exploring enums, there are a few specific blog posts and articles that I've found very helpful in thinking about enums in my own work. These are a few examples on enum driven table views, and an awesome series on pattern matching.

These are a few posts that serve as helpful references on Swift enum features.

And these examples focus on strategies that improve code quality in Swift and have some awesome references for how enums can help you do that.