We now have a pretty incredible and comprehensive suite of navigation tools for the Composable Architecture. We can handle alerts, confirmation dialogs, sheets, popovers, covers, navigation links, and even a new iOS 16 style of navigation destinations. And it doesn’t matter which form of navigation you are trying to implement, the steps to do it are all basically the same. You do a bit of domain modeling, you integrate the parent and child features together using the ifLet
reducer operator, and then you integrate the view by calling the corresponding SwiftUI view modifier.
There is of course a big, gaping hole in our navigation tools, and that is the new iOS 16 NavigationStack
API that takes a binding. This style of navigation is extremely powerful, though sometimes difficult to handle correctly, and we will have a lot to say about that soon, but there is something more important to address first.
While what we have accomplished so far is pretty impressive, after all we have unified 6 different forms of navigation into essentially a single API, we are still modeling our domain in a less than ideal way. Our inventory feature is using 4 pieces of optional state to represent all the different places you can navigate to, and that allows for a lot of non-sensical states which leaks uncertainty into every corner of our codebase.
For example, it’s technically possible for the addItem
and duplicateItem
and editItem
all be non-nil
at the same time. What does that even mean? A sheet, popover, and drill-down would all activated at the same time? And beyond that weirdness we can also never be sure that we know exactly what screen is being displayed at any time. We have to check all 4 optionals to see if something is presented and if 2 or more things are non-nil
we just have to guess at what is actually being displayed.
By using 4 optionals we technically have “2 to the 4th” possible states of things being nil
or non-nil
, which is 16 possibilities. Only 5 of those are valid: either they are all nil
or exactly one is non-nil
. That leads us to believe that a single enum is a much better tool for modeling this domain than a bunch of optionals, and that’s a lesson we learned when discussing vanilla SwiftUI navigation, as well as modern SwiftUI.
We put in a lot of effort to build up navigation tools in vanilla SwiftUI that allowed us to model all destinations as a single enum, and then derive bindings to each case of the enum in order to drive navigation. We need to do the same for the navigation tools we have just built for the Composable Architecture, so let’s see how we can accomplish that.