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)
}
}
}
}
}
这是使用我创建的名为 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 或使用带有数据和选择状态的字符串。
在 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)
}
}
}
}
}
这是使用我创建的名为 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 或使用带有数据和选择状态的字符串。