I am trying to create a simple multiple selection List with SwiftUI. I am unable to make it work.
List takes a second argument which is a SelectionManager, so I tried creating a concrete implementation of one. But, it never gets called and the rows never highlight.
import SwiftUI var demoData = ["Phil Swanson", "Karen Gibbons", "Grant Kilman", "Wanda Green"] struct SelectKeeper : SelectionManager{ var selections = Set<UUID>() mutating func select(_ value: UUID) { selections.insert(value) } mutating func deselect(_ value: UUID) { selections.remove(value) } func isSelected(_ value: UUID) -> Bool { return selections.contains(value) } typealias SelectionValue = UUID } struct SelectionDemo : View { @State var selectKeeper = SelectKeeper() var body: some View { NavigationView { List(demoData.identified(by: \.self)){ name in Text(name) } .navigationBarTitle(Text("Selection Demo")) } } } #if DEBUG struct SelectionDemo_Previews : PreviewProvider { static var previews: some View { SelectionDemo() } } #endif
Code runs fine but rows don't highlight and the SelectionManager code is never called.
To support single selection, first add an optional property of the same type you're using inside your list. For example, if you were using a list of integers you would have an optional Int . Once you have that, pass it to your list using its selection parameter, then make sure your list is in editing mode.
The only way to get multiple selection in SwiftUI right now is by using EditButton . However, that's not the only instance you might want to use multiple selection, and it would probably confuse users if you used EditButton multiple selection when you're not actually trying to edit anything.
Depending on what you want, there are two ways to do this:
You must enable "Edit mode" on the list before a selection matters. From the interface for List
:
/// Creates an instance. /// /// - Parameter selection: A selection manager that identifies the selected row(s). /// /// - See Also: `View.selectionValue` which gives an identifier to the rows. /// /// - Note: On iOS and tvOS, you must explicitly put the `List` into Edit /// Mode for the selection to apply. @available(watchOS, unavailable) public init(selection: Binding<Selection>?, content: () -> Content)
You do that by adding an EditButton
to your view somewhere. After that, you just need to bind a var for something that implements SelectionManager
(you don't need to roll your own here :D)
var demoData = ["Phil Swanson", "Karen Gibbons", "Grant Kilman", "Wanda Green"] struct SelectionDemo : View { @State var selectKeeper = Set<String>() var body: some View { NavigationView { List(demoData.identified(by: \.self), selection: $selectKeeper){ name in Text(name) } .navigationBarItems(trailing: EditButton()) .navigationBarTitle(Text("Selection Demo \(selectKeeper.count)")) } } }
This approach looks like this:
At this point, we're going to have to roll our own. Note: this implementation has a bug which means that only the Text
will cause a selection to occur. It is possible to do this with Button
but because of the change in Beta 2 that removed borderlessButtonStyle()
it looks goofy, and I haven't figured out a workaround yet.
struct Person: Identifiable, Hashable { let id = UUID() let name: String } var demoData = [Person(name: "Phil Swanson"), Person(name: "Karen Gibbons"), Person(name: "Grant Kilman"), Person(name: "Wanda Green")] struct SelectKeeper : SelectionManager{ var selections = Set<UUID>() mutating func select(_ value: UUID) { selections.insert(value) } mutating func deselect(_ value: UUID) { selections.remove(value) } func isSelected(_ value: UUID) -> Bool { return selections.contains(value) } typealias SelectionValue = UUID } struct SelectionDemo : View { @State var selectKeeper = Set<UUID>() var body: some View { NavigationView { List(demoData) { person in SelectableRow(person: person, selectedItems: self.$selectKeeper) } .navigationBarTitle(Text("Selection Demo \(selectKeeper.count)")) } } } struct SelectableRow: View { var person: Person @Binding var selectedItems: Set<UUID> var isSelected: Bool { selectedItems.contains(person.id) } var body: some View { GeometryReader { geo in HStack { Text(self.person.name).frame(width: geo.size.width, height: geo.size.height, alignment: .leading) }.background(self.isSelected ? Color.gray : Color.clear) .tapAction { if self.isSelected { self.selectedItems.remove(self.person.id) } else { self.selectedItems.insert(self.person.id) } } } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With