In UIKit you can select multiple rows of a UITableView by using allowsMultipleSelection - can this be done with the List in SwiftUI?
可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
回答1:
The only way to get multiple selection in SwiftUI right now is by using EditButton
. However, that's not the only instance you might want to use multiple selection, and it would probably confuse users if you used EditButton
multiple selection when you're not actually trying to edit anything.
I assume what you're really looking for is something like this:
Below is the code I wrote to create this:
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")
}
}
}
}
}
回答2:
I created a custom ToggleStyle
as follows:
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)
}
}
}
}
}
}
回答3:
I found an approach using a custom property wrapper that enables the selection to be modified from a child view using a 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
回答4:
Here is an alternate way that uses a helper I created called 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")
}
}
}
}
}
}
With the supporting code here:
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
}
}
}