Select Multiple Items in SwiftUI List

2020-06-16 04:16发布

In UIKit you can select multiple rows of a UITableView by using allowsMultipleSelection - can this be done with the List in SwiftUI?

标签: swiftui
4条回答
霸刀☆藐视天下
2楼-- · 2020-06-16 04:38

I found an approach using a custom property wrapper that enables the selection to be modified from a child view using a Binding:

struct Fruit: Selectable {
    let name: String
    var isSelected: Bool
    var id: String { name }
}

struct FruitList: View {
    @State var fruits = [
        Fruit(name: "Apple", isSelected: true),
        Fruit(name: "Banana", isSelected: false),
        Fruit(name: "Kumquat", isSelected: true),
    ]

    var body: some View {
        VStack {
            Text("Number selected: \(fruits.filter { $0.isSelected }.count)")
            BindingList(items: $fruits) {
                FruitRow(fruit: $0)
            }
        }
    }

    struct FruitRow: View {
        @Binding var fruit: Fruit

        var body: some View {
            Button(action: { self.fruit.isSelected.toggle() }) {
                HStack {
                    Text(fruit.isSelected ? "☑" : "☐")
                    Text(fruit.name)
                }
            }
        }
    }
}

Here is the source for BindingList

查看更多
何必那么认真
3楼-- · 2020-06-16 04:59

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.

I assume what you're really looking for is something like this:

enter image description here

Below is the code I wrote to create this:

struct MultipleSelectionList: View {
    @State var items: [String] = ["Apples", "Oranges", "Bananas", "Pears", "Mangos", "Grapefruit"]
    @State var selections: [String] = []

    var body: some View {
        List {
            ForEach(self.items, id: \.self) { item in
                MultipleSelectionRow(title: item, isSelected: self.selections.contains(item)) {
                    if self.selections.contains(item) {
                        self.selections.removeAll(where: { $0 == item })
                    }
                    else {
                        self.selections.append(item)
                    }
                }
            }
        }
    }
}
struct MultipleSelectionRow: View {
    var title: String
    var isSelected: Bool
    var action: () -> Void

    var body: some View {
        Button(action: self.action) {
            HStack {
                Text(self.title)
                if self.isSelected {
                    Spacer()
                    Image(systemName: "checkmark")
                }
            }
        }
    }
}
查看更多
兄弟一词,经得起流年.
4楼-- · 2020-06-16 05:01

Here is an alternate way that uses a helper I created called Multiselect:

enter image description here

struct Fruit: Selectable {
    let name: String
    var isSelected: Bool
    var id: String { name }
}

struct FruitList: View {

    @State var fruits = [
        Fruit(name: "Apple", isSelected: true),
        Fruit(name: "Banana", isSelected: false),
        Fruit(name: "Kumquat", isSelected: true),
    ]

    var body: some View {
        VStack {
            Text("Number selected: \(fruits.filter { $0.isSelected }.count)")
            Multiselect(items: $fruits) { fruit in
                HStack {
                    Text(fruit.name)
                    Spacer()
                    if fruit.isSelected {
                        Image(systemName: "checkmark")
                    }
                }
            }
        }
    }
}

With the supporting code here:

protocol Selectable: Identifiable {
    var name: String { get }
    var isSelected: Bool { get set }
}

struct Multiselect<T: Selectable, V: View>: View {
    @Binding var items: [T]
    var rowBuilder: (T) -> V

    var body: some View {
        List(items) { item in
            Button(action: { self.items.toggleSelected(item) }) {
                self.rowBuilder(item)
            }
        }
    }
}

extension Array where Element: Selectable {
    mutating func toggleSelected(_ item: Element) {
        if let index = firstIndex(where: { $0.id == item.id }) {
            var mutable = item
            mutable.isSelected.toggle()
            self[index] = mutable
        }
    }
}
查看更多
趁早两清
5楼-- · 2020-06-16 05:03

I created a custom ToggleStyle as follows:

import SwiftUI


enum Fruit: String, CaseIterable, Hashable {
    case apple = "Apple"
    case orange = "Orange"
    case banana = "Banana"
}

struct ContentView: View {

    @State var fruits = [Bool](repeating: false, count: Fruit.allCases.count)

    var body: some View {
        Form{
            ForEach(0..<fruits.count, id:\.self){i in
                Toggle(isOn: self.$fruits[i]){
                    Text(Fruit.allCases[i].rawValue)
                }.toggleStyle(CheckmarkToggleStyle())
            }
        }
    }
}

struct CheckmarkToggleStyle: ToggleStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        HStack {
            Button(action: { withAnimation { configuration.$isOn.wrappedValue.toggle() }}){
                HStack{
                    configuration.label.foregroundColor(.primary)
                    Spacer()
                    if configuration.isOn {
                        Image(systemName: "checkmark").foregroundColor(.primary)
                    }
                }
            }
        }
    }
}

查看更多
登录 后发表回答