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.