Purposes

  • Separate the navigation logic from the view layer. It’ll allow you to change navigation without modifying views.
  • Simplify the implementation of tasks, like deep linking, by making navigation more flexible

SwiftUI-Specific Information

The declarative nature of SwiftUI makes it challenging to separate the navigation from the view layer. Push navigation requires inserting a NavigationLink into a view. And modal presentation needs the .sheet modifier to be added somewhere in the view code. You must provide a destination view for both the NavigationLink and .sheet. Also, in general, you’ll use the @state property to trigger navigation.

This is different from UIKit. For example, to present a view modally in UIKit, it’s enough to call presenton the current view controller and pass a destination view controller to display. That can be easily done from outside of a presenting view controller.

Solutions

We’ll consider two somewhat different solutions to separate the navigation from the view layer. Both use the native SwiftUI navigation methods NavigationLink and .sheet modifier. But providing a destination view and navigation state will be moved to a router.

  1. Router with trigger views: A router will return trigger subviews for all possible navigation routes to insert them into a presenting view. Such a subview code snippet will contain a NavigationLink or .sheet modifier inside, as well as a destination view specified, and will use a @state property, stored in the router, via binding. This way, the presenting view won’t depend on the navigation code and destination, only on a router protocol.
  2. Router with type-erased modifiers: A presenting view will be configured with general modifiers for presenting any other views. Being initialized with a router, those modifiers will track the navigation state stored in the router via bindings and will perform navigation when the router changes that state. The router also will have functions for all possible navigation. Those functions will change the state and trigger navigation as a result.

Example of usage

To make it easier to understand, let’s consider both solutions in the context of a simple example.

The project will consist of two simple views: a presenting view with a single button and a presented view that should be navigated to when the user taps the button. The presented view will display a text passed from the presenting view. Tapping on that text will dismiss the presented view.

1. Router With Trigger Views

Presenting view

The presenting view is initialized with a router that knows how to navigate to the presented view. The router provides a trigger view that, being inserted, can perform the desired navigation in response to user interactions.

Using PresentingRouterProtocol instead of the actual router class allows us to change the navigation logic easily and to initialize the PresentingView with a mock router for tests.

We should wrap the router property with @StateObject (available from iOS 14). This is because the navigation state resides in a router, and the view should update on the state changes. Also, in contrast to @ObservedObject,this wrapperavoids recreating a router every time the owner view is updated. It’s crucial for our implementation of push navigation based on NavigationLink.

#xcode #programming #swift #mobile #swiftui

Routing in SwiftUI
29.70 GEEK