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 present
on the current view controller and pass a destination view controller to display. That can be easily done from outside of a presenting view controller.
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.
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.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.
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