SwiftUI Flow Coordinator pattern to coordinate navigation between views

Michał Ziobro
Mac O’Clock
Published in
4 min readJan 23, 2022

This article covers navigation in SwiftUI pre-iOS 16. If you are interested with implementing flow coordinators for newer versions of iOS starting from iOS 16 I encourage you to look at my updated article here SwiftUI Flow Coordinator pattern with NavigationStack to coordinate navigation between views (iOS 16 +)

In this article I would like to demonstrate you how you can use FlowCoordinators with SwiftUI to separate navigation logic from view logic.
In UIKit this pattern was very popular and such Flow Coordinators enable to push or present new view controllers separating this task from view controllers and view models code. This allow to decouple view controllers view code from its navigation and to easily change flow in application.
Similar thing can be done in SwiftUI to the some extent.

  1. Navigation Primitives in SwiftUI

Most of the navigation in SwiftUI can be done using @Binding that keeps activation state of navigation and SwiftUI special modifiers and views i.e.
fullScreenCover, sheet, alert, confirmationDialog or NavigationLink.

NavigationLink( “Purple”, destination: ColorDetail(color: .purple), isActive: $shouldShowPurple)NavigationLink(tag: .firstLink, selection: activeLink, destination: firstDestination) { EmptyView() }

And to present modals we can use something like this

view .sheet(isPresented: $isShowingSheet, onDismiss: didDismiss) { 
Text(“License Agreement”)
}
view.sheet(item: sheetItem, content: sheetContent)

2. View and ViewModel

Typically we separate presentation logic from business logic (or preparing view data) by splitting code into View and ViewModel.

ViewModel should prepare all necessary data to display in view (outputs), and handle all actions coming from view (inputs).

View should just handle displaying of this data and layouting them on screen.

Simple View can look like this

And ViewModal for this view preparing text to display and handling firstAction like this.

3. Creating Flow Coordinator

In SwiftUI all navigation primitives must be called in context of view to work correctly. So we can do some assumptions about flow coordinators:
1. Flow Coordinator is View
2. We have Flow Coordinator per screen
3. Navigation events should be passed to Flow Coordinator from ViewModel
4. We need some enum that will represent those navigation events

3.1 Creating protocol representing flow coordinator state
This protocol enable us to pass navigation events from view model to flow coordinator.

Here ContentLink is enum that represents different navigation events/actions.

This protocol should be implemented by our ViewModel. This way view model in response to user action can handle them and pass navigation events to flow coordinator via this FlowStateProtocol.

So our complete ContentViewModel processing several user actions and implementing ContentFlowStateProtocol can look like this.

3.2 Creating ContentLink enum for navigation events
This enumaration defines different navigation events that can happen in our screen of application. This events can have some parameters passed along them. Moreover ContentLink enum should be Identifiable and Hashable.

In enumeration we define several computed properties i.e id to to fullfill Identifiable protocol, navigationLink to map parametrized cases of events to its sibling cases, sheetLink to isolate and map cases that should be displayed using modal presentation.

3.3 Implementing per screen FlowCoordinator view

The most essential part of our Flow coordinator patter will be ContentFlowCoordinator view. It will handle all per screen navigation logic.

Firstly I will demonstrate how such coordinator can look and then explain some things.

Firstly its init (here implicit) will have to parameters both using generics.
1. state that is of type implementing ContentFlowStateProtocol.
2. content which will be screen view @ViewBuilder

Secondly state need to be stored as @ObservedObject and it shouldn’t be @StateObject as ContentFlowStateProtocol is implemented by ContentViewModel and this view model will be already stored as @StateObject in screen ContentView.

Thirdly we have helper bindings made as computed properties for NavigationLink i.e. activeLink and for sheet presentation i.e. sheetItem.

All navigation logic is implemented inside ContentFlowCoordinator body computed property. There is added NavigationView, embedded navigationLinks property and attached sheet(item:…) modifier.

Last but not least we have factory functions that build our destination/content views. They extract eventual navigation event parameters, construct view models with them and finally view using this view model.

4. Using FlowCoordinator with View

The final step that remain to complete ContentView screen is to put it all together and implement this view. This is the same view you so at the beginning of this tutorial but with added our brand new ContentFlowCoordinator and extending view model generic type to require to adopt the ContentFlowStateProtocol.

We also added more actions to this view.

If you are interested in full code source where you can clone repository and test this Flow Coordinator pattern in action I encourage you to visit my github link: https://github.com/michzio/FlowCoordinator-in-SwiftUI

--

--