SwiftUI: Working Around no popToRootViewController()

Pacu
2 min readJan 13, 2020

--

SwiftUI Logo

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 Combine
final 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.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Pacu
Pacu

Written by Pacu

Software Developer. Rookie surfer

No responses yet

Write a response