Select SwiftUI 列表中的多个项目

Select Multiple Items in SwiftUI List

在 UIKit 中,您可以 select UITableView 的多行,方法是使用 allowsMultipleSelection - 这可以用 SwiftUI 中的列表来完成吗?

目前在 SwiftUI 中获得多重选择的唯一方法是使用 EditButton。但是,这并不是您可能想要使用多项选择的唯一实例,如果您在实际不尝试编辑任何内容时使用 EditButton 多项选择,则可能会使用户感到困惑。

我假设您真正要找的是这样的东西:

下面是我为创建它而编写的代码:

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: { [=10=] == 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")
                }
            }
        }
    }
}

我创建了一个自定义 ToggleStyle 如下:

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

我找到了一种使用自定义 属性 包装器的方法,该包装器允许使用 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 { [=10=].isSelected }.count)")
            BindingList(items: $fruits) {
                FruitRow(fruit: [=10=])
            }
        }
    }

    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

这是使用我创建的名为 Multiselect 的助手的替代方法:

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 { [=10=].isSelected }.count)")
            Multiselect(items: $fruits) { fruit in
                HStack {
                    Text(fruit.name)
                    Spacer()
                    if fruit.isSelected {
                        Image(systemName: "checkmark")
                    }
                }
            }
        }
    }
}

这里有支持代码:

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: { [=11=].id == item.id }) {
            var mutable = item
            mutable.isSelected.toggle()
            self[index] = mutable
        }
    }
}

首先将其添加到您的视图中

@State var selectedItems = Set<UUID>()

Set 的类型取决于您用于 id: ForEach

中项目的类型

接下来声明列表

List(selection: $selectedItems) {
    ForEach(items, id: \.id) { item in
        Text("\(item.name)")
    }
}

现在无论你 select 被添加到 selectedItems Set 记得在你使用它之后清除它。

我的 2 美分超级简单的解决方案:

import SwiftUI

struct ListDemo: View {
    @State var items = ["Pizza", "Spaghetti", "Caviar"]
    @State var selection = Set<String>()
    
    var body: some View {
        List(items, id: \.self, selection: $selection) { (item : String) in
            
            let s = selection.contains(item) ? "√" : " "
            
            HStack {
                Text(s+item)
                Spacer()
            }
            .contentShape(Rectangle())
            .onTapGesture {
                if  selection.contains(item) {
                    selection.remove(item)
                }
                else{
                    selection.insert(item)
                }
                print(selection)
            }
        }
        .listStyle(GroupedListStyle())
    }
}

在集合中使用字符串不是最佳选择,最好使用 id 或使用带有数据和选择状态的字符串。