top of page

Router Pattern for SwiftUI Navigation

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:

  1. 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.

  2. 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.

  3. 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:


swiftui router view containing a router object that manages swiftui views

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.

Site
Join the growing community of iOS engineers who are taking their skills to the next level.

© 2025 Curious Algorithm. All rights reserved.

bottom of page