Search code examples
swiftuiswiftui-navigationlinkdismiss

How to return to the starting view from the navigation tree?


Let's say I have an array that I am appending first with one element in another view, and then appending with the second element from the third view. How to return to the starting view and deduce two additional views from memory?

    struct ContentView: View {
    @State var array: [String] = []
    
    var body: some View {
        NavigationView{
            ScrollView{
                ForEach(array, id: \.self) { element in
                    Text(element)
                }
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    NavigationLink("+") {
                        Details(array: $array)
                    }
                }
            }
        }
    }
}

struct Details: View {
    @Binding var array: [String]
    var body: some View {
        Button(action: { array.append("First Element") }) {
            Text("Append First Element")
        }
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                NavigationLink("+") {
                }
            }
        }
    }
}

struct MoreDetails: View {
    @Binding var array: [String]
    @Environment(\.dismiss) var dismiss

    var body: some View {
        Button(action: { array.append("Second Element") }) {
            Text("Append Second Element")
        }
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                NavigationLink("Done") {
                    ContentView(array: array)
                }
                .simultaneousGesture(TapGesture().onEnded{
                    dismiss()
                })
            }
        }
    }
}

Solution

  • The issue you are having really is one of navigation, or at least how you think you want to navigate. What you have set up in terms of navigation is this:

    Original ContentView -> Details -> MoreDetails -> New ContentView
    

    When what you wanted was:

    Original ContentView -> Details -> MoreDetails -> Original ContentView
    

    without having to navigate back through all of the views. To do this, essentially you build a house of cards and then pull the bottom card out, and your navigation collapses back to the original ContentView. Commented code:

    struct ContentView: View {
        @State var array: [String] = []
        // originalIsActive is your bottom card, and you will carry a reference
        // through your views like a piece of twine.
        @State var originalIsActive = false
        
        var body: some View {
            NavigationView{
                ScrollView{
                    ForEach(array, id: \.self) { element in
                        Text(element)
                    }
                }
                .background(
                    // This is a programatic NavigationLink that triggers when originalIsActive
                    // becomes true. Placed in a background it sits and listens for originalIsActive.
                    NavigationLink(isActive: $originalIsActive, destination: {
                        Details(array: $array, originalIsActive: $originalIsActive)
                    }, label: {
                        // This is simply a nothing view, so you can't see the NavigationLink.
                        EmptyView()
                    })
                    // Necessary to prevent link pop back after this NavigationLink
                    .isDetailLink(false)
                )
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            // This Button sets originalIsActive to true, activating the NavigationLink.
                            originalIsActive = true
                        } label: {
                            Image(systemName: "plus")
                        }
                    }
                }
            }
        }
    }
    
    struct Details: View {
        @Binding var array: [String]
        // Reference to originalIsActive
        @Binding var originalIsActive: Bool
        
        @State var detailsIsActive = false
        
        var body: some View {
            Button(action: { array.append("First Element") }) {
                Text("Append First Element")
            }
            .background(
                NavigationLink(isActive: $detailsIsActive, destination: {
                    MoreDetails(array: $array, originalIsActive: $originalIsActive)
                }, label: {
                    EmptyView()
                })
                // Necessary to prevent link pop back after this NavigationLink
                .isDetailLink(false)
            )
            .toolbar {
                Button {
                    detailsIsActive = true
                } label: {
                    Image(systemName: "plus")
                }
            }
        }
    }
    
    struct MoreDetails: View {
        @Binding var array: [String]
        // Reference to originalIsActive
        @Binding var originalIsActive: Bool
    
        var body: some View {
            Button(action: { array.append("Second Element") }) {
                Text("Append Second Element")
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        // By setting originalIsActive to false, you pull out the bottom card.
                        // If the first link does not exist, none do.
                        originalIsActive = false
                    } label: {
                        Text("Original")
                    }
    
                }
            }
        }
    }