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
}
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.