在 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 列表中选择多个项目的主要内容,如果未能解决你的问题,请参考以下文章