如果包裹在 NavigationView (macOS) 中,SwiftUI 列表选择绑定会在每次点击时更新两次

SwiftUI List selection binding updates twice on every click, if wrapped in NavigationView (macOS)

鉴于下面的代码,我做了一个奇怪的观察(在 macOS 上)。如果一个 List 被包裹在一个 NavigationView 中,我突然在一行上每次点击得到两个更新(一个在 mouseDown 上——也就是按住鼠标点击而不释放它,一个在 mouseUp 上——也就是释放鼠标点击)。这不会发生在一个简单的列表上,或者如果它被包装在一个 HStack 中。有谁知道我为什么以及如何控制/改变这种行为?

查看实际的 HStack 版本:

查看实际使用的 NavigationView 版本:

struct ContentView: View {
    @State var selection: Set<Int> = []

    var body: some View {
        List(0..<20, selection: Binding(get: {
            self.selection
        }, set: { val in
            print("set \(val)")
            self.selection = val
        })) { idx in
            Text("\(idx)")
        }

        Color.red
        Color.green
        Color.blue
    }
}

// Wrapped in a HStack, 1 update per row selection is triggered, as expected!
struct HStackVersion: View {
    var body: some View {
        HStack(spacing:0.0) {
            ContentView()
        }
    }
}

// Wrapped in a NavigationView, 2 updates per row selection are triggered??
struct NavigationViewVersion: View {
    var body: some View {
        NavigationView {
            ContentView()
        }
    }
}

下面的屏幕截图显示了 setter 中断点的堆栈跟踪,显示 SwiftUI.ListCoreCoordinator 设置了两次绑定。我在 Map 中看到过类似的问题。我不确定对绑定 setters 的重复调用是否是他们设计的一部分,但可以理解,因为 SwiftUI 将所有状态更改合并为对 body.[=19 的单个调用=]

如果您想解决此问题,那么我建议您使用更标准的 onChange 修饰符而不是自定义 BindingonChange 仅调用一次新值,例如

struct MacListProbView: View {
    @State var selection: Set<Int> = []

    var body: some View {        
        List(0..<20, selection: $selection) { idx in
            Text("\(idx)")
        }
        .onChange(of: selection) { newSelection in
            print("set \(newSelection)")
        }
    }
}

struct MacListProbViewNav: View {
    var body: some View {
        NavigationView {
            MacListProbView()
            Color.red // note it's more efficient to have these here because they are not affected by the selection value.
            Color.green
            Color.blue
        }
    }
}