在SwiftUI列表中选择多个项目

时间:2019-07-13 20:30:19

标签: swiftui

在UIKit中,您可以使用allowsMultipleSelection选择UITableView的多行-可以使用SwiftUI中的列表来完成此操作吗?

6 个答案:

答案 0 :(得分:5)

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

我认为您真正想要的是这样的东西:

enter image description here

下面是我为此创建的代码:

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.identified(by: \.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")
                }
            }
        }
    }
}

答案 1 :(得分:2)

我发现了一种使用自定义属性包装器的方法,该方法可以使用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

答案 2 :(得分:2)

这是使用我创建的名为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")
                    }
                }
            }
        }
    }
}

此处提供了支持代码:

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

答案 3 :(得分:2)

我的 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 或使用带有数据和选择状态的字符串。

答案 4 :(得分:1)

我创建了一个自定义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)
                    }
                }
            }
        }
    }
}

答案 5 :(得分:0)

首先将其添加到您的视图

@State var selectedItems = Set<UUID>()

Set的类型取决于您用来id:中的项目ForEach的类型

接下来声明列表

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

现在,您选择的任何内容都会添加到selectedItems Set中,使用后请记住将其清除。