I'm seeing onChange(of: Bool) action tried to update multiple times per frame
warnings when clicking on NavigationLink
s in the sidebar for a SwiftUI macOS App.
Here's what I currently have:
import SwiftUI
@main
struct BazbarApp: App {
@StateObject private var modelData = ModelData()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(modelData)
}
}
}
class ModelData: ObservableObject {
@Published var myLinks = [URL(string: "https://google.com")!, URL(string: "https://apple.com")!, URL(string: "https://amazon.com")!]
}
struct ContentView: View {
@EnvironmentObject var modelData: ModelData
@State private var selected: URL?
var body: some View {
NavigationView {
List(selection: $selected) {
Section(header: Text("Bookmarks")) {
ForEach(modelData.myLinks, id: \.self) { url in
NavigationLink(destination: DetailView(selected: $selected) ) {
Text(url.absoluteString)
}
.tag(url)
}
}
}
.onDeleteCommand {
if let selected = selected {
modelData.myLinks.remove(at: modelData.myLinks.firstIndex(of: selected)!)
}
selected = nil
}
Text("Choose a link")
}
}
}
struct DetailView: View {
@Binding var selected: URL?
var body: some View {
if let selected = selected {
Text("Currently selected: \(selected)")
}
else {
Text("Choose a link")
}
}
}
When I alternate clicking on the second and third links in the sidebar, I eventually start seeing the aforementioned warnings in my console.
Here's a gif of what I'm referring to:
Interestingly, the warning does not appear when alternating clicks between the first and second link.
Does anyone know how to fix this?
I'm using macOS 12.2.1 & Xcode 13.2.1.
Thanks in advance
CodePudding user response:
I think the issue is that both the List(selection:)
and the NavigationLink
are trying to update the state variable selected
at once. A List(selection:)
and a NavigationLink
can both handle the task of navigation. The solution is to abandon one of them. You can use either to handle navigation.
Since List
look good, I suggest sticking with that. The NavigationLink
can then be removed. The second view under NavigationView
is displayed on the right, so why not use DetailView(selected:)
there. You already made the selected
parameter a binding variable, so the view will update if that var changes.
struct ContentView: View {
@EnvironmentObject var modelData: ModelData
@State private var selected: URL?
var body: some View {
NavigationView {
List(selection: $selected) {
Section(header: Text("Bookmarks")) {
ForEach(modelData.myLinks, id: \.self) { url in
Text(url.absoluteString)
.tag(url)
}
}
}
.onDeleteCommand {
if let selected = selected {
modelData.myLinks.remove(at: modelData.myLinks.firstIndex(of: selected)!)
}
selected = nil
}
DetailView(selected: $selected)
}
}
}