6

What is the correct way to combine the mentioned views.

As of now, i have a NavigationStack at the bottom of my app. It displays a LaunchView as root. When a user is authenticated, the main view is added to the stack, if not, the login/ register views are added to the stack. This works fine.

NavigationStack(path: self.$vm.path) {
        LaunchView()
            .navigationDestination(for: Authentication.self) { value in
                switch value {
                    case .login:
                        LoginView(vm: LoginViewModel() { type, action in
                            ...
                        })
                    case .verify: // let .verify(email)
                        VerifyMailView(vm: VerifyMailViewModel() { type, action in
                            ...
                        })
                    case .authenticated:
                        AuthenticatedView() { type, action in
                            self.vm.set(type: type) { value in
                                ...
                            }
                        }
                }
            }
        
    }

The AuthenticatedView consists of a TabView with three views. Here comes the issue. I assumed i could set the title and toolbars of the three views directly on them, which is not the case. However i don't want nor can set the titles or the toolbars within the tabview, as they require data from the views viewmodels (to which the TabView has, and should not have, no access).

TabView(selection: self.$vm.index) {
        DiscoverView(vm: DiscoverViewModel(account: self.vm.$account, type: self.type))
        .tabItem {
            Image(self.vm.index == 0 ? "discover.selected" : "discover")
        }
        .tag(0)
        
        MatchesView(vm: MatchesViewModel(type: self.type))
        .tabItem {
            Image(self.vm.index == 1 ? "matches.selected" : "matches")
        }
        .tag(1)
        
        ProfileView(vm: ProfileViewModel(account: self.vm.$account, type: self.type))
        .tabItem {
            Image(self.vm.index == 2 ? "profile.selected" : "profile")
        }
        .tag(2)
    }
    .toolbar(.hidden, for: .navigationBar)

The only workaround i found is to hide the navigationbar in the TabView and to set new NavigationViews within the child views.

        NavigationView {
            VStack {
                ...
            }
            .navigationTitle(Text("discover"))
            .toolbar {
                ...
            }
        }

This, however, feels not correct as it is a NavigationView inside a NavigationView and even is buggy (the title and toolbar are sometimes placed below their normal position, kind of like below the hidden, but still exisiting navigation bar of the TabView).

Thus, has anyone found a solution of how to properly combine a NavigationStack with a TabView ?

9
  • 6
    A TabView cannot be inside a NavigationStack but each tabItem can have its own NavigationStack. This is in the apple guidelines. If you have a TabView it should always be visible. People can get around this by making their own but apple does not provide this functionality. Sep 10, 2022 at 12:04
  • 1
    There is a difference between a NavigationStack and a NavigationView. You are saying that one can never use both, a NavigationStack and a TabView, within an app. This would mean you would miss out on programatic navigation if you were to implement a TabView, which does not make sense to me.
    – Lukas
    Sep 10, 2022 at 12:46
  • I didn’t say within an app I said that the navigation has to be in each tab not outside the tab view. Look up the tab bar design guidelines. Sep 10, 2022 at 12:59
  • 4
    This is correct, however i am talking about a NavigationStack not a NavigationView. And it makes no sense to create multiple stacks for each tab, as there should only be one.
    – Lukas
    Sep 10, 2022 at 13:05
  • That is how apple wants it, it isn’t about making sense. You can circumvent this by making your own tab view which only takes a few lines of code. Sep 10, 2022 at 13:07

1 Answer 1

4

I found lorem ipsum's comments to be enlightening, so I built a sample app based on their ideas.

The following Swift code is an example for how a basic app can be built, where each page in a TabView has its own NavigationStack.

import SwiftUI

struct ContentView: View {
    var body: some View {
        TabView {
            AirplaneView()
                .tabItem {
                    Label("Airplane Tab", systemImage: "airplane.circle")
                }
            PlanetView()
                .tabItem {
                    Label("Planet Tab", systemImage: "globe.americas.fill")
                }
        }
    }
}

struct AirplaneView: View {
    var body: some View {
        NavigationStack {
            VStack {
                Text("Airplane tab view")
                Divider()
                NavigationLink(destination: NestedItemA()) {
                    Text("Go to nested airplane view")
                }
                .navigationTitle("Airplane")
            }
        }
    }
}

struct PlanetView: View {
    var body: some View {
        NavigationStack {
            VStack {
                Text("Planet tab view")
                Divider()
                NavigationLink(destination: NestedItemB()) {
                    Text("Go to nested planet view")
                }
                .navigationTitle("Planet")
            }
        }
    }
}

struct NestedItemA: View {
    var body: some View {
        NavigationStack {
            Text("Airplane goes vroom")
                .navigationTitle("Nested airplane")
        }
    }
}

struct NestedItemB: View {
    var body: some View {
        NavigationStack {
            Text("Planet spin good")
                .navigationTitle("Nested planet")
        }
    }
}

Here is an animated preview.

1
  • Let's say that I click "Go to nested airplane view" and I am obviously seeing NestedItemA View. But now I'd like click on AirplaneIcon in TabView to navigate to AirplaneView. Currently I need to click back button to achieve that? Jan 6 at 17:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.