Below I have created a simple NavigationSplitView for MacOS using Swift. Both the sidebar and the detail page rely on an Observable value. When I switch that observable value both sidebar and detail update accordingly....... Until the sidebar invokes a NavigationLink to same detail page. At that point changing the Observed value only updates the sidebar and not the detail page. This is a major pain for my App navigation and I am wondering if anyone knows why the NavigationSplitView detail page stops responding to the change in the observed value. I guess it could be stacking views on top of each other when a navigationLink is called.
import SwiftUI
import Foundation
internal import Combine
final class ModelData: ObservableObject {
@Published var value: Int = 1
}
struct ContentView: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationSplitView{
sidebarPage(v: modelData.value)
}detail:{
detailPage(v: modelData.value)
}
}
}
struct sidebarPage: View{
@EnvironmentObject var modelData: ModelData
var v: Int
var body: some View {
//Show an indication that NavigationSplitView Sidebar is updating when modelData.selectedTab is altered
Text("Sidebar version")
Text("\(v)").font(.largeTitle)
//Switch button which changes the value of the modelData.selectedTab which will be seen to have two behaviours
//When pressed repeatedly it changes the NavigationSplitView Sidebar and detail view, simply by altering the modelData.selectedTab value
//This behaviour then changes once a NavigationLink is used
Text("Switching changes sidebar and detail pages").font(.caption)
Button("Switch", action: {modelData.value = 3 - modelData.value})
//NavigationLink alter the detail page
Text("Until a NavigationLink is invoked").font(.caption)
NavigationLink ("Detail Page 1", destination: detailPage(v: 1))
NavigationLink ("Detail Page 2", destination: detailPage(v: 2))
}
}
struct detailPage: View{
@EnvironmentObject var modelData: ModelData
var v: Int
var body: some View {
//Show a change in v in detail page
Text("Detail Page")
Text("\(v)").font(.largeTitle)
.onChange(of: v) {
//when the value of the page changes, update the modelData.selectedTab value accordingly.
//This changes NavigationSplitView Sidebar page
modelData.value = v
}
}
}
Any explanations as to why and how it can be overcome would be gratefully received
When you activate a NavigationLink
in the sidebar, the destination view gets "pushed" onto the detail column. So after you click on a navigation link, the detail view is either displaying detailPage(v: 1)
or detailPage(v: 2)
. It is no longer displaying the original detailPage(v: modelData.value)
that you put in detail: { ... }
.
Since you passed a constant (1
or 2
) to the v
parameter, the detailPage
will always display that, regardless of what model.value
is.
The use of onChange
is totally redundant. Before you click on the navigation links, the detail column is detailPage(v: modelData.value)
, so v
is the same as modelData.value
anyway. After you click on the navigation link, v
is a constant that never changes, so onChange
cannot possibly be triggered at all. Keep in mind that the navigation links push a new view to replace the original detail column - these are separate views, each with their own onChange
detecting changes of their own v
s.
In the first place though, this is not how NavigationLink
s are designed to be used in the sidebar column of a NavigationSplitView
. The only documented way to use NavigationLink
s in the sidebar column is "coordinate with a list". That is,
final class ModelData: ObservableObject {
@Published var value: Int = 1
}
struct ContentView: View {
@StateObject var modelData = ModelData()
var body: some View {
NavigationSplitView{
List(selection: $modelData.value) {
NavigationLink("Detail Page 1", value: 1)
NavigationLink("Detail Page 2", value: 2)
}
}detail:{
Text("Detail Page")
Text("\(modelData.value)").font(.largeTitle)
}
.environmentObject(modelData)
}
}