I have a NavigationSplitView
with all three columns, and NavigationLinks
in the sidebar to present different List
s for the content column. When a list item is selected in the content view, I want to present a view in the detail view. Since different detail views should be presented for different content views, I represent state like this (some details omitted for space):
// Which detail view to show in the third column
enum DetailViewKind: Equatable {
case blank
case localEnv(RegistryEntry)
case package(SearchResult)
}
// Which link in the sidebar was tapped
enum SidebarMenuItem: Int, Hashable, CaseIterable {
case home
case localEnvs
case remoteEnvs
case packageSearch
}
// Binds to a DetailViewKind, defines the activate SidebarMenuItem
struct SidebarView: View {
@State private var selectedMenuItem: SidebarMenuItem?
@Binding var selectedDetailView: DetailViewKind
var body: some View {
VStack(alignment: .leading, spacing: 32) {
SidebarHeaderView()
Divider()
// Creates the navigationLinks with a SidebarMenuRowView as labels
SidebarMenuView(selectedMenuItem: $selectedMenuItem, selectedDetailView: $selectedDetailView)
Spacer()
Divider()
SidebarFooterView()
}
.padding()
.frame(alignment: .leading)
}
}
struct SidebarMenuRowView: View {
var menuItem: SidebarMenuItem
@Binding var selectedMenuItem: SidebarMenuItem?
@Binding var selectedDetailView: DetailViewKind
private var isSelected: Bool {
return menuItem == selectedMenuItem
}
var body: some View {
HStack {
Image(systemName: menuItem.systemImageName).imageScale(.small)
Text(menuItem.title)
Spacer()
}
.padding(.leading)
.frame(height: 24)
.foregroundStyle(isSelected ? Color.primaryAccent : Color.primary)
.background(isSelected ? Color.menuSelection : Color.clear)
.clipShape(RoundedRectangle(cornerRadius: 10))
.navigationDestination(for: SidebarMenuItem.self) { item in
navigationDestinationFor(menuItem: item, detailView: $selectedDetailView)
}
.onTapGesture {
if menuItem != selectedMenuItem {
selectedMenuItem = menuItem
}
}
}
}
// Determines which detail view to present
struct DetailView: View {
@Binding var selectedDetailView: DetailViewKind
var innerView: some View {
switch selectedDetailView {
case .blank:
AnyView(Text("Make a selection")
.font(.subheadline)
.foregroundStyle(.secondary)
.navigationSplitViewColumnWidth(min: 200, ideal: 350))
case .localEnv(let regEntry):
AnyView(EnvironmentDetailView(regEntry: regEntry))
case .package(let searchResult):
AnyView(PackageDetailView(searchResult: searchResult))
}
}
var body: some View {
innerView
}
}
struct ContentView: View {
@State private var detailView: DetailViewKind = .blank
var body: some View {
NavigationSplitView {
SidebarView(selectedDetailView: $detailView)
.navigationSplitViewColumnWidth(175)
} content: {
HomeView()
.navigationSplitViewColumnWidth(min: 300, ideal: 450)
}
detail: {
DetailView(selectedDetailView: $detailView)
}
}
}
My issue is that the detail view is not updated when the ContentView
's detailView
property is updated. I've verified that the value itself is changing, but the view is not. I searched around to see why this would be (this is my first Swift/SwiftUI application) and from what I gather a @Binding
alone will not cause a view to be updated, that binding either needs to be used in the view hierarchy, or it needs to be stored as a @State
property that gets updated when the binding value changes. I added a dummy @State
property to DetailView
and that still doesn't work, so I'm a little confused:
struct DetailView: View {
@Binding var selectedDetailView: DetailViewKind
@State private var dummyProp: DetailViewKind?
var innerView: some View {
switch selectedDetailView {
case .blank:
AnyView(Text("Make a selection")
.font(.subheadline)
.foregroundStyle(.secondary)
.navigationSplitViewColumnWidth(min: 200, ideal: 350))
case .localEnv(let regEntry):
AnyView(EnvironmentDetailView(regEntry: regEntry))
case .package(let searchResult):
AnyView(PackageDetailView(searchResult: searchResult))
}
}
var body: some View {
innerView.onChange(of: selectedDetailView) {
dummyProp = selectedDetailView
}
}
}
Any ideas?