Select Multiple Items in SwiftUI List

前端 未结 6 920
遥遥无期
遥遥无期 2021-02-04 11:37

In UIKit you can select multiple rows of a UITableView by using allowsMultipleSelection - can this be done with the List in SwiftUI?

相关标签:
6条回答
  • 2021-02-04 12:10

    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")
                    }
                }
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-04 12:13

    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)
                        }
                    }
                }
            }
        }
    }
    
    
    0 讨论(0)
  • 2021-02-04 12:26

    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

    0 讨论(0)
  • 2021-02-04 12:26

    my 2 cents with a super simple solution:

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

    using string in set is sub-optimal, better to use id OR using strict with data and selection state.

    0 讨论(0)
  • 2021-02-04 12:33

    First add this to your view

    @State var selectedItems = Set<UUID>()
    

    The type of the Set depends on the type you use to id: the items in the ForEach

    Next declare the List

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

    Now whatever you select gets added to the selectedItems Set remember to clear it out after you use it.

    0 讨论(0)
  • 2021-02-04 12:36

    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
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题