# What is navigation? Learn about the two main forms of state-driven navigation, tree-based and stack-based navigation, as well as their tradeoffs. ## Overview State-driven navigation broadly falls into 2 main categories: tree-based, where you use optionals and enums to model navigation, and stack-based, where you use flat collections to model navigation. Nearly all navigations will use a combination of the two styles, but it is important to know their strengths and weaknesses. * [Tree-based navigation](#Tree-based-navigation) * [Stack-based navigation](#Stack-based-navigation) * [Tree-based vs stack-based navigation](#Tree-based-vs-stack-based-navigation) * [Pros of tree-based navigation](#Pros-of-tree-based-navigation) * [Cons of tree-based navigation](#Cons-of-tree-based-navigation) * [Pros of stack-based navigation](#Pros-of-stack-based-navigation) * [Cons of stack-based navigation](#Cons-of-stack-based-navigation) ## Defining navigation The word "navigation" can mean a lot of different things to different people. For example, most people would say that an example of "navigation" is the drill-down style of navigation afforded to us by `NavigationStack` in SwiftUI and `UINavigationController` in UIKit "navigation". However, if drill-downs are considered navigation, then surely sheets and fullscreen covers should be too. The only difference is that sheets and covers animate from bottom-to-top instead of from right-to-left, but is that actually substantive? And if sheets and covers are considered navigation, then certainly popovers should be too. We can even expand our horizons to include more styles of navigation, such as alerts and confirmation dialogs, and even custom forms of navigation that are not handed down to us from Apple. So, for the purposes of this documentation, we will use the following loose definition of "navigation": > Definition: **Navigation** is a change of mode in the application. Each of the examples we considered above, such as drill-downs, sheets, popovers, covers, alerts, dialogs, and more, are all a "change of mode" in the application. But, so far we have just defined one term, "navigation", by using another undefined term, "change of mode", so we will further make the following definition: > Definition: A **change of mode** is when some piece of state goes from not existing to existing, or vice-versa. So, when a piece of state switches from not existing to existing, that represents a navigation and change of mode in the application, and when the state switches back to not existing, it represents undoing the navigation and returning to the previous mode. That is very abstract way of describing state-driven navigation, and the next two sections make these concepts much more concrete for the two main forms of state-driven navigation: [tree-based](#Tree-based-navigation) and [stack-based](#Stack-based-navigation) navigation. ## Tree-based navigation In the previous section we defined state-driven navigation as being controlled by the existence or non-existence of state. The term "existence" was not defined, and there are a few ways in which existence can be defined. If we define the existence or non-existence of state as being represented by Swift's `Optional` type, then we call this "tree-based" navigation because when multiple states of navigation are nested they form a tree-like structure. For example, suppose you have an inventory feature with a list of items such that tapping one of those items performs a drill-down navigation to a detail screen for the item. Then that can be modeled with the ``PresentationState`` property wrapper pointing to some optional state: ```swift @Reducer struct InventoryFeature { struct State { @PresentationState var detailItem: DetailItemFeature.State? // ... } // ... } ``` Then, inside that detail screen there may be a button to edit the item in a sheet, and that too can be modeled with a ``PresentationState`` property wrapper pointing to a piece of optional state: ```swift @Reducer struct DetailItemFeature { struct State { @PresentationState var editItem: EditItemFeature.State? // ... } // ... } ``` And further, inside the "edit item" feature there can be a piece of optional state that represents whether or not an alert is displayed: ```swift @Reducer struct EditItemFeature { struct State { @PresentationState var alert: AlertState? // ... } // ... } ``` And this can continue on and on for as many layers of navigation that exist in the application. With that done, the act of deep-linking into the application is a mere exercise in constructing a piece of deeply nested state. So, if we wanted to launch the inventory view into a state where we are drilled down to a particular item _with_ the edit sheet opened _and_ an alert opened, we simply need to construct the piece of state that represents the navigation: ```swift InventoryView( store: Store( initialState: InventoryFeature.State( detailItem: DetailItemFeature.State( // Drill-down to detail screen editItem: EditItemFeature.State( // Open edit modal alert: AlertState { // Open alert TextState("This item is invalid.") } ) ) ) ) { InventoryFeature() } ) ``` In the above we can start to see the tree-like structure of this form of domain modeling. Each feature in your application represents a node of the tree, and each destination you can navigate to represents a branch from the node. Then the act of navigating to a new feature corresponds to building another nested piece of state. That is the basics of tree-based navigation. Read the dedicated article for information on how to use the tools that come with the Composable Architecture to implement tree-based navigation in your application. ## Stack-based navigation In the [previous section](#Tree-based-navigation) we defined "tree-based" navigation as the process of modeling the presentation of a child feature with optional state. This takes on a tree-like structure in which a deeply nested feature is represented by a deeply nested piece of state. There is another powerful tool for modeling the existence and non-existence of state for driving navigation: collections. This is most used with SwiftUI's `NavigationStack` view in which an entire stack of features are represented by a collection of data. When an item is added to the collection it represents a new feature being pushed onto the stack, and when an item is removed from the collection it represents popping the feature off the stack. Typically one defines an enum that holds all of the possible features that can be navigated to on the stack, so continuing the analogy from the previous section, if an inventory list can navigate to a detail feature for an item and then navigate to an edit screen, this can be represented by: ```swift enum Path { case detail(DetailItemFeature.State) case edit(EditItemFeature.State) // ... } ``` Then a collection of these states represents the features that are presented on the stack: ```swift let path: [Path] = [ .detail(DetailItemFeature.State(item: item)), .edit(EditItemFeature.State(item: item)), // ... ] ``` This collection of `Path` elements can be any length necessary, including very long to represent being drilled down many layers deep, or even empty to represent that we are at the root of the stack. That is the basics of stack-based navigation. Read the dedicated article for information on how to use the tools that come with the Composable Architecture to implement stack-based navigation in your application. ## Tree-based vs stack-based navigation Most real-world applications will use a mixture of tree-based and stack-based navigation. For example, the root of your application may use stack-based navigation with a `NavigationStack` view, but then each feature inside the stack may use tree-based navigation for showing sheets, popovers, alerts, etc. But, there are pros and cons to each form of navigation, and so it can be important to be aware of their differences when modeling your domains. #### Pros of tree-based navigation * Tree-based navigation is a very concise way of modeling navigation. You get to statically describe all of the various navigation paths that are valid for your application, and that makes it impossible to restore a navigation that is invalid for your application. For example, if it only makes sense to navigate to an "edit" screen after a "detail" screen, then your detail feature needs only to hold onto a piece of optional edit state: ```swift struct State { @PresentationState var editItem: EditItemFeature.State? // ... } ``` This statically enforces the relationship that we can only navigate to the edit screen from the detail screen. * Related to the previous pro, tree-based navigation also allows you to describe the finite number of navigation paths that your app supports. * If you modularize the features of your application, then those feature modules will be more self-contained when built with the tools of tree-based navigation. This means that Xcode previews and preview apps built for the feature will be fully functional. For example, if you have a `DetailFeature` module that holds all of the logic and views for the detail feature, then you will be able to navigate to the edit feature in previews because the edit feature's domain is directly embedded in the detail feature. * Related to the previous pro, because features are tightly integrated together it makes writing unit tests for their integration very simple. You can write deep and nuanced tests that assert how the detail feature and edit feature integrate together, allowing you to prove that they interact in the correct way. * Tree-based navigation unifies all forms of navigation into a single, concise style of API, including drill-downs, sheets, popovers, covers, alerts, dialogs and a lot more. See for more information. #### Cons of tree-based navigation * Unfortunately it can be cumbersome to express complex or recursive navigation paths using tree-based navigation. For example, in a movie application you can navigate to a movie, then a list of actors in the movies, then to a particular actor, and then to the same movie you started at. This creates a recursive dependency between features that can be difficult to model in Swift data types. * By design, tree-based navigation couples features together. If you can navigate to an edit feature from a detail feature, then you must be able to compile the entire edit feature in order to compile the detail feature. This can eventually slow down compile times, especially when you work on features closer to the root of the application since you must build all destination features. * Historically, tree-based navigation is more susceptible to SwiftUI's navigation bugs, in particular when dealing with drill-down navigation. However, many of these bugs have been fixed in iOS 16.4 and so is less of a concern these days. #### Pros of stack-based navigation * Stack-based navigation can easily handle complex and recursive navigation paths. The example we considered earlier, that of navigating through movies and actors, is handily accomplished with an array of feature states: ```swift let path: [Path] = [ .movie(/* ... */), .actors(/* ... */), .actor(/* ... */) .movies(/* ... */), .movie(/* ... */), ] ``` Notice that we start on the movie feature and end on the movie feature. There is no real recursion in this navigation since it is just a flat array. * Each feature held in the stack can typically be fully decoupled from all other screens on the stack. This means the features can be put into their own modules with no dependencies on each other, and can be compiled without compiling any other features. * The `NavigationStack` API in SwiftUI typically has fewer bugs than `NavigationLink(isActive:)` and `navigationDestination(isPresented:)`, which are used in tree-based navigation. There are still a few bugs in `NavigationStack`, but on average it is a lot more stable. #### Cons of stack-based navigation * Stack-based navigation is not a concise tool. It makes it possible to expressive navigation paths that are completely non-sensical. For example, even though it only makes sense to navigate to an edit screen from a detail screen, in a stack it would be possible to present the features in the reverse order: ```swift let path: [Path] = [ .edit(/* ... */), .detail(/* ... */) ] ``` That is completely non-sensical. What does it mean to drill down to an edit screen and _then_ a detail screen. You can create other non-sensical navigation paths, such as multiple edit screens pushed on one after another: ```swift let path: [Path] = [ .edit(/* ... */), .edit(/* ... */), .edit(/* ... */), ] ``` This too is completely non-sensical, and it is a drawback to the stack-based approach when you want a finite number of well-defined navigation paths in your app. * If you were to modularize your application and put each feature in its own module, then those features, when run in isolation in an Xcode preview, would be mostly inert. For example, a button in the detail feature for drilling down to the edit feature can't possibly work in an Xcode preview since the detail and edit features have been completely decoupled. This makes it so that you cannot test all of the functionality of the detail feature in an Xcode preview, and instead have to resort to compiling and running the full application in order to preview everything. * Related to the above, it is also more difficult to unit test how multiple features integrate with each other. Because features are fully decoupled we cannot easily test how the detail and edit feature interact with each other. The only way to write that test is to compile and run the entire application. * And finally, stack-based navigation and `NavigationStack` only applies to drill-downs and does not address at all other forms of navigation, such as sheets, popovers, alerts, etc. It's still on you to do the work to decouple those kinds of navigations. --- We have now defined the basic terms of navigation, in particular state-driven navigation, and we have further divided navigation into two categories: tree-based and stack-based. Continue reading the dedicated articles and to learn about the tools the Composable Architecture provides for modeling your domains and integrating features together for navigation.