SwiftUI: Detect NavigationView goes back to main view

958 Views Asked by At

I have a NavigationView and the following view structure ViewA -> ViewB -> ViewC and I'm trying to figure out how to detect when ViewB goes back to ViewAViewB -> ViewA. Here is my Code:

struct ViewA: View {
    var body: some View {
        NavigationView {
            NavigationLink{
                ViewB()
            }label: {
                Text("Go to ViewB")
                
            }
        }
    }
}

struct ViewB: View {
    @Environment(\.presentationMode) var presentationMode: Binding
    var body: some View {
        NavigationLink{
            ViewC()
        }label: {
            Text("Go to ViewC")
        }
        .onAppear{
            print("onAppear \(presentationMode.wrappedValue.isPresented)")
        }
        .onDisappear{
            print("onDisappear \(presentationMode.wrappedValue.isPresented)")
        }
    }
}

struct ViewC: View {
    var body: some View {
        Text("ViewC")
    }
}

With the following code:

        .onAppear{
            print("onAppear \(presentationMode.wrappedValue.isPresented)")
        }
        .onDisappear{
            print("onDisappear \(presentationMode.wrappedValue.isPresented)")
        }

with the code above I can detect when ViewC goes ViewB (per console output onAppear true). But my question is how can detect when ViewB -> ViewA.

Any of you knows how can accomplish this or if there is a way around to detect this?

I'll really appreciate your help

3

There are 3 best solutions below

1
On

If you want to keep using NavigationView for compatibility with iOS 15 and earlier, then you can reliably be notified when ViewB is popped by making the state of ViewA’s NavigationLink explicit.

You used the NavigationLink(destination:label:) initializer, which means the NavigationLink has a hidden flag that tracks whether it is active or not.

Instead, store that flag yourself, and use a custom Binding with the NavigationLink(isActive:destination:label:) initializer to be notified when the NavigationView activates or deactivates the link.

struct ViewA: View {
    @State var linkIsActive = false
    
    var body: some View {
        NavigationView {
            NavigationLink(isActive: Binding(
                get: { linkIsActive },
                set: {
                    if $0 != linkIsActive {
                        linkIsActive = $0
                        print($0 ? "pushing ViewB" : "popping ViewB")
                    }
                }
            )) {
                ViewB()
            } label: {
                Text("Go to ViewB")
            }
        }
        .navigationViewStyle(.stack)
    }
}
0
On

Though both of approaches are correct, mentioned by @ChrisR is also working well. If i was working on it i would have used environmentObject something like below approach. Hope it helps. Working well tested on Xcode 14.3

class NavigationLinkDestination: ObservableObject {
    @Published var currentView: String = ""
}

struct ContentView: View {
    @StateObject var navigationLinkDest = NavigationLinkDestination()
    var body: some View {
        NavigationView {
            NavigationLink{
                ViewB()
            }label: {
                Text("Go to ViewB").onAppear {
                    navigationLinkDest.currentView = "ViewA"
                    print("current view \(navigationLinkDest.currentView)")
                }
                
            }
        }.environmentObject(navigationLinkDest)
    }
}

struct ViewB: View {
    @EnvironmentObject var navigationLinkDest: NavigationLinkDestination
    var body: some View {
        NavigationLink{
            ViewC()
        }label: {
            Text("Go to ViewC").onAppear {
                navigationLinkDest.currentView = "View B"
                print("current view \(navigationLinkDest.currentView)")
            }
        }.environmentObject(navigationLinkDest)
    }
}


struct ViewC: View {
    @EnvironmentObject var navigationLinkDest: NavigationLinkDestination
    var body: some View {
        Text("ViewC").onAppear {
            navigationLinkDest.currentView = "View C"
            print(navigationLinkDest.currentView)
        }.environmentObject(navigationLinkDest)
    }
}

0
On

Here is a solution based on the new NavigationStack which lets you record the navigation path (as @workingdog mentioned):

struct ViewA: View {
    
    @State private var navigationPath: [String] = []
    
    var body: some View {
        NavigationStack(path: $navigationPath) {
            NavigationLink(value: "B") {
                Text("Go to ViewB")
            }
            .navigationDestination(for: String.self) { destination in
                if destination == "B" {
                    ViewB()
                } else {
                    ViewC()
                }
            }
        }
        .onChange(of: navigationPath) { newValue in
            print(newValue)
            if newValue == [] {
                print("Coming from ViewB to ViewA") // trigger your action here
            }
        }
    }
}

struct ViewB: View {

    var body: some View {
        NavigationLink(value: "C") {
            Text("Go to ViewC")
        }
    }
}

struct ViewC: View {
    
    var body: some View {
        Text("ViewC")
    }
}