soleil
soleil

Reputation: 13135

Routing in NavigationStack not working as expected

I have a full code example below. The problem is that when I tap "Go to Detail" in the DogsView, the detail view is not visually pushed, although the path does get added to the path array for that router. If I tap the Cats tab and come back to the Dogs tab, I will then see the detail view. Tapping back (in the nav bar) from there returns me to the Dogs root view. What am I doing wrong?

(Note - the same thing happens if I go to the cats tab and tap "Push Stats". The StatsView won't appear until I tap the Dogs tab and then tap back to the Cats tab)

Another problem is that tapping "Go to Cats Tab" has no effect. I would expect the tab to switch since the TabView selection is based on $routeProvider.tabRouter.currentTab

Any other tips on setting up a routing system to navigate within a tab and to other tabs appreciated!

enum PetTab: Hashable {
    case dogs
    case cats
}

class PetTabRouter: ObservableObject {
    @Published var currentTab: PetTab = .dogs
}


enum PetView: Hashable {
    case detailView(itemId: String)
    case statsView
}


class AppRouter: ObservableObject {
    @Published var path: [PetView] = []
    @Published var isModalPresented: Bool = false
    @Published var modalItem: PetView?
    var tabRouter: PetTabRouter

    init(tabRouter: PetTabRouter) {
        self.tabRouter = tabRouter
    }

    func navigate(to destination: PetView) {
        path.append(destination)
    }
    
    func pop() {
        if !path.isEmpty {
            path.removeLast()
        }
    }
    
    func present(_ item: PetView) {
        modalItem = item
        isModalPresented = true
    }
    
    func dismiss() {
        if isModalPresented {
            isModalPresented = false
            modalItem = nil
        }
        else {
            pop()
        }
    }

}

class RouteProvider: ObservableObject {
    @Published var tabRouter = PetTabRouter()

    lazy var dogsRouter: AppRouter = {
        return AppRouter(tabRouter: tabRouter)
    }()
    
    lazy var catsRouter: AppRouter = {
        return AppRouter(tabRouter: tabRouter)
    }()
    
}


struct ContentView: View {
    @StateObject private var routeProvider = RouteProvider()
    
    var body: some View {
        TabView(selection: $routeProvider.tabRouter.currentTab) {
            
            DogsView()
                .tabItem { Label("Dogs", systemImage: "dog") }
                .tag(PetTab.dogs)
            
            CatsView()
                .tabItem { Label("Cats", systemImage: "cat") }
                .tag(PetTab.cats)

        }
        .environmentObject(routeProvider)
    }

}

struct DogsView: View {

    @EnvironmentObject var routeProvider: RouteProvider

    var body: some View {
        NavigationStack(path: $routeProvider.dogsRouter.path) {
            VStack {
                Text("Dogs")
                Button("Go to Detail") {
                    routeProvider.dogsRouter.navigate(to: .detailView(itemId: "123"))
                }
                Button("Go to Cats Tab") {
                    routeProvider.tabRouter.currentTab = .cats
                }
                Button("Go to Cats Tab and Push Detail View") {
                    routeProvider.catsRouter.navigate(to: .detailView(itemId: "456"))
                }
            }
            .navigationDestination(for: PetView.self) { destination in
                switch destination {
                case .detailView(let itemId):
                    DetailView(itemId: itemId)
                case .statsView:
                    StatsView()
                }
            }
        }

    }
}

struct CatsView: View {
    @EnvironmentObject var routeProvider: RouteProvider

    var body: some View {
        NavigationStack(path: $routeProvider.catsRouter.path) {
            VStack {
                Text("Cats")
                Button("Push Stats") {
                    routeProvider.catsRouter.navigate(to: .statsView)
                }
            }
            .navigationDestination(for: PetView.self) { item in
                switch item {
                case .detailView(let itemId):
                    DetailView(itemId: itemId)
                case .statsView:
                    StatsView()
                }
            }
        }
    }
}



struct DetailView: View {
    let itemId: String
    @EnvironmentObject var routeProvider: RouteProvider

    var body: some View {
        VStack {
            Text("Detail View for item \(itemId)")
            
            Button("Stats View Modal") {
                //???
            }
            
            Button("Back to Home") {
                //???
            }
        }
    }
}

struct StatsView: View {
    @EnvironmentObject var routeProvider: RouteProvider

    var body: some View {
        VStack {
            Text("Stats")
            Button("Dismiss") {
                //???
            }
        }
        .padding()
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.teal)
    }
}

Upvotes: 1

Views: 45

Answers (0)

Related Questions