swiftswiftuiswiftui-navigationlink

Automatically "Back" action after List item selection in NavigationLink


I have some screen in iOS app with NavigationStack (or NavigationView) which contains different elements. One of them is searchable List which is wrapped in NavigationLink. So this searchable list appears in new screen.

And when user selects some element of List he should to be automatically returned back to previous screen without additional two clicks on Cancel (if use search) and then "Back" buttons.

Does exist any right way to do this? Thanks for any advice!

import SwiftUI

struct TheItem: Identifiable {
    let id = UUID()
    let name: String
}

class SomeModel: ObservableObject {
    
    @Published var selectedId: UUID?
    @Published var searchText: String = ""

    var items: [TheItem] = [
        TheItem(name:"Item 1"),
        TheItem(name:"Item 2"),
        TheItem(name:"Item 3"),
        // ...
        TheItem(name:"Item N")
    ]
    
    var filteredItems: [TheItem] {
        guard !searchText.isEmpty else { return self.items }
        return self.items.filter { item in
            item.name.lowercased().contains(self.searchText.lowercased())
        }
    }
    
    var selectedItemName: String {
        if selectedId != nil {
            return filteredItems.first(where: {$0.id == selectedId})?.name ?? "-"
        }
        return "-"
    }
    
    init () {
        selectedId = items.first?.id
    }
}

struct TestNavigationLinkBackkView: View {
    
    @EnvironmentObject private var model: SomeModel
    
    var body: some View {
        NavigationStack {
            NavigationLink(model.selectedItemName) {
                List {
                    Picker("Item", selection: $model.selectedId) {
                        ForEach(model.filteredItems) { item in
                            Text(item.name).tag(item.id)
                        }
                    }
                }
                .searchable(text: $model.searchText, placement: .navigationBarDrawer(displayMode: .always))
                .pickerStyle(.inline)
            }
        }
    }
}

#Preview {
    TestNavigationLinkBackkView()
        .environmentObject(SomeModel())
}

Solution

  • If you factor out the List part to a separate View then you can call dismiss when the selection changes. This will pop the current view from the NavigationStack.

    The documentation to dismiss states:

    The specific behavior of the action depends on where you call it from.

    This is why it is necessary to factor out the child view to a separate View.

    // TestNavigationLinkBackkView
    
    NavigationStack {
        NavigationLink(model.selectedItemName) {
            ChildView()
        }
    }
    
    struct ChildView: View {
        @EnvironmentObject private var model: SomeModel
        @Environment(\.dismiss) private var dismiss // 👈 added
    
        var body: some View {
            List {
                Picker("Category", selection: $model.selectedId) {
                    ForEach(model.filteredItems) { item in
                        Text(item.name).tag(item.id)
                    }
                }
            }
            .searchable(text: $model.searchText, placement: .navigationBarDrawer(displayMode: .always))
            .pickerStyle(.inline)
            .onChange(of: model.selectedId) { // 👈 added
                dismiss()
            }
        }
    }