Deselect Items from SwiftUI List on macOS with single click
I'm developing a macOS App with a List
View with selectable rows.
As there is unfortunately no editMode
on macOS, Single Click Selection of Cells is possible, but deselecting an already selected Cell doing the same does nothing.
The only option to deselect the Cell is to CMD + Click
which is not very intuitive.
Minimum Example:
struct RowsView: View {
@State var selectKeeper: String?
let rows = ["1", "2", "3", "4", "5", "6", "7", "8"]
var body: some View {
List(rows, id: \.self, selection: $selectKeeper) { row in
Text(row)
}
}
}
struct RowsView_Previews: PreviewProvider {
static var previews: some View {
RowsView()
}
}
Clicking Row Nr 3 with a Single Click or even double Click does nothing and the row stays selected.
Attaching Binding directly
I have tried to attach the Binding
directly as described in the excelent answer for a Picker
here, but this does not seem to work for List
on macOS:
...
var body: some View {
List(rows, id: \.self, selection: Binding($selectKeeper, deselectTo: nil)) { row in
Text(row)
}
}
...
public extension Binding where Value: Equatable {
init(_ source: Binding<Value>, deselectTo value: Value) {
self.init(get: { source.wrappedValue },
set: { source.wrappedValue = $0 == source.wrappedValue ? value : $0 }
)
}
}
Any ideas on how single click deselect can be made possible without rebuilding the selection mechanism?
For the record: XCode 13.2.1, macOS BigSur 11.6.2
We can block default click handling by using own gesture and manage selection manually.
Here is demo of possible approach. Tested with Xcode 13.2 / macOS 12.1
struct RowsView: View {
@State var selectKeeper: String?
let rows = ["1", "2", "3", "4", "5", "6", "7", "8"]
var body: some View {
List(rows, id: \.self, selection: $selectKeeper) { row in
Text(row)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.contentShape(Rectangle()) // handle click row-wide
.listRowInsets(EdgeInsets()) // remove default edges
.onTapGesture {
selectKeeper = selectKeeper == row ? nil : row // << here !!
}
.padding(.vertical, 4) // look&feel like default
}
}
}