Router Pattern for SwiftUI Navigation
- Eric Palma
- Nov 4, 2023
- 3 min read
Updated: Mar 29
The navigation APIs in SwiftUI are simple to use but they come with a tradeoff: they couple our navigation logic with our views.
To decouple our view code from our navigation code we can employ a well-suited design pattern. In the realm of UIKit, one such pattern we have available is the Coordinator pattern.
However, if our apps are built exclusively in SwiftUI, we need a different solution that suits the SwiftUI approach to navigation. This is where the Router pattern comes into play and in this article we will learn what it is and how we can employ it in our apps.
The case for using the Router pattern for navigation
In SwiftUI, if we want to push a view using a navigation stack, we would do it something like this:
I acknowledge that this involves significantly less code compared to the equivalent process in UIKit, but still there a few problems with this code:
ViewA knows that we are using a NavigationStack to display ViewB, because it literally contains it! If we wanted to present ViewB using a sheet instead, we would need to make several updates to ViewA.
On top of knowing how to present ViewB, ViewA needs to know what value is associated with the view. We can see this in the NavigationLink and the navigationDestination view modifier.
Probably the biggest offense in my opinion, ViewA is responsible for building ViewB!
All of this results in our two views being coupled to each other, meaning any updates to ViewB have a high probability of affecting ViewA. For example, if ViewB needs new data to be initialized, ViewA must be updated to provide it. In a different case, if we no longer want to push ViewB from ViewA, again ViewA will need to be modified significantly. Coupling between the two views is what is causes all these updates related to ViewB to affect ViewA
Our view code should only focus on displaying data to users and capturing user input, ideally it should not also be responsible for navigation logic. This is known as the single responsibility principle, and it’s a good principle to follow if we want our code to be clean, maintainable, and scalable.
To ensure our views have only one responsibility, we need to move the navigation logic elsewhere. One way to do this is to introduce a Router.
Using the Router pattern for SwiftUI Navigation
As stated above, our goal is to move all navigation related code out of our views and into a Router. The following image illustrates how we could use a Router:

The RouterView will be a SwiftUI view where we place the SwiftUI navigation APIs. For simplicity, we'll only be using a NavigationStack. Below is my simple implementation of the RouterView:
Join my newsletter for insights, tips, and 25% off any subscription!
With the RouterView in place, the Router object will be responsible for building the views contained in our RouterView, managing navigation between the views, and containing any conditional flow logic required. Below is how we can implement a Router:
Want to read more?
Subscribe to curiousalgorithm.com to keep reading this exclusive post.