在 SwiftUI 列表中选择多个项目

Posted

技术标签:

【中文标题】在 SwiftUI 列表中选择多个项目【英文标题】:Select Multiple Items in SwiftUI List 【发布时间】:2019-07-13 20:30:19 【问题描述】:

在 UIKit 中,您可以使用 allowsMultipleSelection 选择 UITableView 的多行 - 这可以通过 SwiftUI 中的 List 来完成吗?

【问题讨论】:

是的。查看如何使用EditButton。 hackingwithswift.com/quick-start/swiftui/… @dfd - 多选和多行编辑看起来是不同的东西。 @Rob,不确定您的意思。从技术上讲,allowsMultipleSelection 对于List 不存在(目前)。但在功能上,如果您将 EditButton 与 List 一起使用,则可以选择多行,尽管 非常UITableView 不同。 这正是我的意思。它们是相似的,但完全不同的东西。 我能否以编程方式点击 EditButton 以便在显示视图时可以选择多个项目?用例将多个标签应用于文件 【参考方案1】:

目前在 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:  $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")
                
            
        
    

【讨论】:

谢谢@graycampbell。你的解决方案很棒。 我的完整解决方案基于您的示例:pawelmadej.com/post/multi-select-picker-for-swiftui @graycampbell 我没有使用状态,而是使用“@Binding”,有时我会出现奇怪的行为。例如,单击一行将推送(它在 NavigationView 内)MultipleSelectionList 的新视图... @PeterWarbo 如果没有看到您的代码,很难说为什么会发生这种情况。如果您想发布一个新问题,然后在此处链接到它,我很乐意查看它 祝福你。最佳解决方案。如果需要,您可以使用 Stateable List 发送回另一个视图。谢谢!【参考方案2】:

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

@State var selectedItems = Set<UUID>()

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

接下来声明列表

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

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

【讨论】:

【参考方案3】:

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


【讨论】:

【参考方案4】:

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

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:

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

【讨论】:

如果您有多个部分,这也可能吗?所以你的项目会像[["Pizza", "Spaghetti"], ["Caviar"]]? 是的,但我更喜欢使用 viewModel 或更复杂的解决方案,有点超出这个示例。

以上是关于在 SwiftUI 列表中选择多个项目的主要内容,如果未能解决你的问题,请参考以下文章

更改选项卡式视图栏颜色 SwiftUI

SwiftUI-将@State变量传递给多个视图麻烦

SwiftUI:选择列表中的项目后更新数据

取消选择 SwiftUI 列表中的项目会使应用程序崩溃

SwiftUI 列表内容通过选择重置

将侧栏添加到项目列表以跳到 SwiftUI 中选择的字母表