'SwiftUI: How to switch to a new navigation stack with NavigationViews

I am currently using SwiftUI Beta 5. I have a workflow which involves navigating through a series of views. The last view involves an operation which populates a load of data into the app and ends that particular workflow.

Once the data has been downloaded, the user should be able to start new workflow(s). I would like to "forget" about the old NavigationView, since there is no use in going back through the navigation stack once the workflow has completed. Instead, I would like to then navigate to a "launch" view which effectively becomes the root of a new navigation view.

How can one view within a navigation stack be used to navigate to another view with a different NavigationView (and hence becomes a root for a new navigation stack) using SwiftUI NavigagationViews?



Solution 1:[1]

This is how we solved this question: We had a main/root/launch view, from which the user could tap a button to start a business workflow of some kind. This would open a sheet, which would display a modal pop-up view. (The width and height of sheets can be customised, in order to take up most/all of the screen.)

The sheet will have a NavigationView. This would allow the user to step through a series of views as part of their workflow. The "presented" flag gets passed as a binding from the main view to each navigated view.

When the user reaches the last view and taps a Submit/Done/Finish button to end that particular workflow, the "presented" binding can be set to false, which closes the modal pop-up, and returns the user back to the main view.

Solution 2:[2]

First, sorry I wanted to post a simple comment but not enough reputation point :(

I just updated my way to go back to the root you at stackoverflow.com/a/57513566/7786555

You actually gave me the idea with your comment for new way to go back to the root. Having a new root view. If you force the refresh of your struct view managing the root view then it will automatically do what you want. Here under is only going back to the root (without animation). You can adapt the example to change the root view (instead of using the same) to suit your need.

struct DetailViewB: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    @State var fullDissmiss:Bool = false
    var body: some View {
        SGNavigationChildsView(fullDissmiss: self.fullDissmiss){
            VStack {
                Text("This is Detail View B.")

                Button(action: { self.presentationMode.wrappedValue.dismiss() } )
                { Text("Pop to Detail View A.") }

                Button(action: {
                    self.fullDissmiss = true
                } )
                { Text("Pop two levels to Master View with SGGoToRoot.") }
            }
        }
    }
}

struct DetailViewA: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    @State var fullDissmiss:Bool = false
    var body: some View {
        SGNavigationChildsView(fullDissmiss: self.fullDissmiss){
            VStack {
                Text("This is Detail View A.")

                NavigationLink(destination: DetailViewB() )
                { Text("Push to Detail View B.") }

                Button(action: { self.presentationMode.wrappedValue.dismiss() } )
                { Text("Pop one level to Master.") }

                Button(action: { self.fullDissmiss = true } )
                { Text("Pop one level to Master with SGGoToRoot.") }
            }
        }
    }
}

struct MasterView: View {
    var body: some View {
        VStack {
            Text("This is Master View.")
            NavigationLink(destination: DetailViewA() )
            { Text("Push to Detail View A.") }
        }
    }
}

struct ContentView: View {

    var body: some View {
        SGRootNavigationView{
            MasterView()
        }
    }
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

struct SGRootNavigationView<Content>: View where Content: View {
    let cancellable = NotificationCenter.default.publisher(for: Notification.Name("SGGoToRoot"), object: nil)

    let content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    @State var goToRoot:Bool = false

    var body: some View {
        return
            Group{
            if goToRoot == false{
                NavigationView {
                content()
                }
            }else{
                NavigationView {
                content()
                }
            }
            }.onReceive(cancellable, perform: {_ in
                DispatchQueue.main.async {
                    self.goToRoot.toggle()
                }
            })
    }
}

struct SGNavigationChildsView<Content>: View where Content: View {
    let notification = Notification(name: Notification.Name("SGGoToRoot"))

    var fullDissmiss:Bool{
        get{ return false }
        set{ if newValue {self.goToRoot()} }
    }

    let content: () -> Content

    init(fullDissmiss:Bool, @ViewBuilder content: @escaping () -> Content) {
        self.content = content
        self.fullDissmiss = fullDissmiss
    }

    var body: some View {
        return Group{
            content()
        }
    }

    func goToRoot(){
        NotificationCenter.default.post(self.notification)
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Andy Thomas
Solution 2 Fabrice Leyne