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

Given the below code, I made a strange observation (on macOS). If a List is wrapped in a NavigationView I suddenly get two updates per click on a row (one on mouseDown - aka holding the mouse click down and not releasing it, one on mouseUp - aka releasing the mouse click). This doesn't happen on a plain simple list, or if it's wrapped in an HStack instead. Does anyone know why and how I can control / change this behavior?

See HStack version in action:

HStack based

See NavigationView version in action:

NavigationView based

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()
        }
    }
}


Solution 1:[1]

The screenshot below shows the stacktrace for a breakpoint in the setter showing that SwiftUI.ListCoreCoordinator is setting the binding twice. I've seen a similar problem with Map here. I'm not sure if duplicate calls to binding setters is part of their design or not but it understandably could be, given that SwiftUI coalesces all state changes into a single call to body.

If you'd like to work around the issue then I would suggest going with the more standard onChange modifier instead of that custom Binding, onChange is only called once for the new value, e.g.

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
        }
    }
}

xcode stacktrace and breakpoints

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 malhal