I'm trying to use NavigationSplitView
with a DetailView
that has a task
or onAppear
in it, but it seems it only runs once.
enum MenuItem: String, Hashable, Identifiable, CaseIterable {
case menu1
case menu2
case menu3
case menu4
case menu5
var id: String { rawValue }
}
struct ContentView: View {
@State var selection: MenuItem?
var body: some View {
NavigationSplitView {
List(MenuItem.allCases, selection: $selection) { item in
NavigationLink(value: item) {
Text(item.rawValue)
}
}
} detail: {
if let selection {
DetailView(menuItem: selection)
} else {
Text("Default")
}
}
}
}
struct DetailView: View {
let menuItem: MenuItem
@State var name = "Name"
var body: some View {
VStack {
Text(menuItem.id)
Text(name)
}
.task {
// This should be an async setup code
// but for the sake of simplicity
// I just made it like this
name = menuItem.id
}
}
}
I know I can use onChange(of: selection)
as a workaround, and then have my setup code there. But is there any other way to make task
or onAppear
work inside my DetailView
?
Basing the View's id
on the selection is not a good idea. It will force an entire body
rebuild every time the selection changes, which will result in sluggish performance as the view hierarchy grows.
Instead, you can use the alternate form of task(id:priority:_:) to initiate the task when the selection
value changes, like so:
struct ContentView: View {
@State var selection: MenuItem?
var body: some View {
NavigationSplitView {
…
} detail: {
…
}
.task(id: selection, priority: .userInitiated) { sel in
print("selection changed:", sel)
}
}
}