I'm experiencing a problem using programmatic navigation using TabView and NavigationStack. I have a TabView with a $selection pointing to a NavigationPath in an environmentObject. I also have two views to tab to. When clicking on a navigationLink item, a DataDetailView opens. Then switching to the second tab, navigationPath should be deleted. When programmatically navigation from tab 2 to tab 1 and changing the navigationPath, an empty view is displayed instead of my DataDetailView(someItem):
Expected behavior: SomeView1 -> (Clicking on item) -> DataDetailView -> (changing tab) -> SomeView2 -> (clicking button) -> DataDetailView(someItem)
Experienced behavior: SomeView1 -> (Clicking on item) -> DataDetailView -> (changing tab) -> SomeView2 -> (clicking button) -> empty view
Can someone explain this behavior? I haven't been successful googling the problem. :/ I could probably do this with a binding of NavigationPath, but in my full app, an environmentObject would be beneficial.
Thanks in advance!
import SwiftUI
struct MyData: Identifiable, Hashable {
let id = UUID().uuidString
let name: String
}
class AppManager: ObservableObject {
@Published var tabSelection: Int = 1
@Published var navigationPath = NavigationPath()
}
class DataManager: ObservableObject {
static let instance = DataManager()
@Published var dataArray = [MyData(name: "Data1"), MyData(name: "Data2"), MyData(name: "Data3"), MyData(name: "Data4")]
}
struct NavigationTest: View {
@EnvironmentObject var appManager: AppManager
var body: some View {
TabView(selection: $appManager.tabSelection) {
SomeView1()
.tabItem { Text("View 1") }
.tag(1)
SomeView2()
.tabItem { Text("View 2") }
.tag(2)
}
}
}
struct SomeView1: View {
@EnvironmentObject var appManager: AppManager
@EnvironmentObject var testData: DataManager
var body: some View {
NavigationStack(path: $appManager.navigationPath) {
List {
ForEach(testData.dataArray) { data in
NavigationLink("\(data.name)", value: data)
}
}
.navigationDestination(for: MyData.self) { data in
DataDetailView(data: data)
}
}
.onDisappear {
if !appManager.navigationPath.isEmpty {
appManager.navigationPath.removeLast()
}
}
}
}
struct DataDetailView: View {
let data: MyData
var body: some View {
Text(data.name).background(.yellow)
}
}
struct SomeView2: View {
@EnvironmentObject var appManager: AppManager
var body: some View {
Button("Segue to someItem") {
let someItem = DataManager.instance.dataArray[2]
appManager.navigationPath.append(someItem)
appManager.tabSelection = 1
}
}
}
#Preview {
@StateObject var appManager = AppManager()
@StateObject var dataManager = DataManager.instance
return NavigationTest()
.environmentObject(appManager)
.environmentObject(dataManager)
}
You can fix it by adding a little delay.
First Change the Tab, then after a fraction of a second change the path.
Here is the code for SomeView2:
struct SomeView2: View {
@EnvironmentObject var appManager: AppManager
var body: some View {
Button("Segue to someItem") {
let someItem = DataManager.instance.dataArray[2]
// First change the Tab.
appManager.tabSelection = 1
// Then change the path.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
appManager.navigationPath.append(someItem)
}
}
}
}