
So, you need to close that viewHierarchy and don’t know how to begin… I’ve been there. There are a lot of hacks around it. I’ll share what I find most swifty of all.
If you haven't, please take a look at this WWDC session: “Data Flow Through SwiftUI”. https://developer.apple.com/videos/play/wwdc2019/226
This workaround is completely based on the concepts explained on it.
The Idea
implementing a close button on the right navigation bar that dismisses the whole navigation flow.
Environment Object to the rescue
an environment object is just data that’s outside view’s and that can modify their state.
final class NavigationFlowObject: ObservableObject {
@Published var isActive: Bool = false
}
So first you have to inject your root view with the environment object on SceneDelegate (or wherever it suits you)
// Create the SwiftUI view that provides the window contents.let contentView = RootView().environmentObject(NavigationFlowObject())
And then, let’s put it to work!
import SwiftUI
import Foundation
import Combinefinal class NavigationFlowObject: ObservableObject {
@Published var isActive: Bool = false
}struct ContentView: View {
var body: some View{
ZStack {
Text("last stop")
.navigationBarHidden(false)
.navigationBarBackButtonHidden(true)
.navigationBarTitle("", displayMode: .inline)
.navigationBarItems(trailing: CloseButton(action: {
self.flow.isActive = false
}))
}
}
}struct SecondView: View {
var body: some View {
NavigationLink(destination:
ThirdView()
) {
Text("push me")
}.isDetailLink(false)
}
}struct ThirdView: View {
var body: some View {
NavigationLink(destination:
ContentView()
.navigationBarHidden(false)
.navigationBarBackButtonHidden(true)
.navigationBarTitle("third", displayMode: .inline)) {
Text("push me")
}.isDetailLink(false)
}
}struct CloseButton: View {
var action: () -> Void
var body: some View {
Button(action: {
self.action()
}) {
Image(systemName: "xmark")
}
}
}struct RootView: View {
@EnvironmentObject var navigationFlow: NavigationFlowObject
var body: some View {
NavigationView {
ZStack {
NavigationLink(destination: SecondView()
.navigationBarTitle("second", displayMode: .inline)
, isActive: $navigationFlow.isActive){
EmptyView()
}.isDetailLink(false)
Button(action: {
self.navigationFlow.isActive = true
}) {
Text("Push me")
}
}
}
}
}
Fundamental ingredients
Each navigation link has the isDetailLink(false) modifier so that this is not treated as a detail on splitview kind of hierarchies
isActive: $navigationFlow.isActive)
is only on the RootView. You can bind isActive to other conditions further up, but you cannot use this binding in more than one place at the same time. Otherwise weirdness is ensured.
EnvironmentObject avoids you propagating the binding throughout your different views.