swiftswiftuiswiftui-tabviewios-navigationview

SwiftUI TabView not working due to NavigationLink hierarchy


I have a TabView with three Views which are my main views when the app is launched. In some Views, I have NavigationLinks to open DetailViews. When these are dismissed, the user is returned to the View in the TabView that the user was on originally before opening a NavigationLink. However, from then on setting the selection variable for the TabView programmatically does not work. I have three custom buttons to navigate through the TabView Views as an alternative to swiping left and right.

When the buttons are pressed, the TabView index indicator changes, so I suppose the TabView has registered the change in the binding variable, however, the View does not change. Swiping left and right does work.

On my NavigationView I use a custom back button which seems to be the issue. When using .navigationBarBackButtonHidden(true), the issue occurs. Otherwise it works fine.

How can I solve this while using my custom back button?

struct MainView: View {
    
    @StateObject var viewRouter: ViewRouter = ViewRouter()
    
    var body: some View {
        VStack(spacing: 0) {
    
            NavigationView {
            TabView(selection: $viewRouter.currentView) {
                Text("Leaderboard")
                    .tag(Views.leaderboard)
                
                Text("Home")
                    .tag(Views.home)
                
                PouleView()
                    .tag(Views.poules)
            }
            .tabViewStyle(.page(indexDisplayMode: .always))
            .ignoresSafeArea()
            }
            .navigationViewStyle(StackNavigationViewStyle())              
            
            VStack {
                HStack(alignment: .center, spacing: 30) {
                    Text("Leaderboard")
                        .foregroundColor(.black)
                        .onTapGesture {
                            viewRouter.changeView(destination: Views.leaderboard)
                        }
                    
                    Text("Home")
                        .foregroundColor(.black)
                        .onTapGesture {
                            viewRouter.changeView(destination: Views.home)
                        }
                    
                    Text("Poule")
                        .foregroundColor(.black)
                        .onTapGesture {
                            viewRouter.changeView(destination: Views.poules)
                        }
                    
                }
            }
            .frame(maxWidth: .infinity)
        }
    }
}
struct PouleView: View {
    
    @State var navigateToDetailView: Bool = false

    var body: some View {
        VStack(spacing: 0) {
            
            NavigationLink(destination: PouleDetailView() , isActive: $navigateToDetailView) { EmptyView() }

            Button(action: {
                self.navigateToDetailView = true
            }) {
               Text("Open Detail")
            }
            
        }
    }
}


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

    var body: some View {
        VStack {
            Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }) {
               Text("Close")
            }
            Text("This is content")
        }
        .navigationBarTitle("",displayMode: .inline)
        .navigationBarBackButtonHidden(true)
        .navigationBarHidden(true)

    }
}
class ViewRouter: ObservableObject {
    
    @Published var onboarding: Bool = false
    @Published var currentView: Views = .home
    @Published var originView: Views = .home
    
    func changeView(destination: Views) {
        withAnimation() {
            self.originView = self.currentView
            self.currentView = destination
        }
    }
}

enum Views: Equatable, Hashable {
    case home
    case poules
    case leaderboard
}

Solution

  • Short answer: put your NavigationStacks (or NavigationViews) -inside- the single tabs, not outside of them.

    Long answer: Navigation patterns are a complicated topic. There is a lot of content out there, and I would suggest starting with the Apple WWDC talks about this. Your current root view is the tab view, instead of a specific tab that is returned to, so that is what is being “returned” to by the navigation.