web-dev-qa-db-fra.com

Comment activer la sélection dans la liste SwiftUI

J'essaie de créer une liste de sélection multiple simple avec SwiftUI. Je suis incapable de le faire fonctionner.

La liste prend un deuxième argument qui est un SelectionManager, j'ai donc essayé de créer une implémentation concrète de celui-ci. Mais, il n'est jamais appelé et les lignes ne sont jamais mises en évidence.

import SwiftUI

var demoData = ["Phil Swanson", "Karen Gibbons", "Grant Kilman", "Wanda Green"]

struct SelectKeeper : SelectionManager{
    var selections = Set<UUID>()

    mutating func select(_ value: UUID) {
        selections.insert(value)
    }

    mutating func deselect(_ value: UUID) {
        selections.remove(value)
    }

    func isSelected(_ value: UUID) -> Bool {
        return selections.contains(value)
    }

    typealias SelectionValue = UUID

}

struct SelectionDemo : View {
    @State var selectKeeper = SelectKeeper()

    var body: some View {
        NavigationView {
            List(demoData.identified(by: \.self)){ name in
                Text(name)
            }
                .navigationBarTitle(Text("Selection Demo"))
        }
    }
}

#if DEBUG
struct SelectionDemo_Previews : PreviewProvider {
    static var previews: some View {
        SelectionDemo()
    }
}
#endif

Le code fonctionne correctement mais les lignes ne sont pas mises en surbrillance et le code SelectionManager n'est jamais appelé.

16
Rumbles

Selon ce que vous voulez, il y a deux façons de procéder:

Si vous souhaitez le faire en "mode édition":

Vous devez activer le "Mode édition" sur la liste avant qu'une sélection soit importante. Depuis l'interface de List:

    /// Creates an instance.
    ///
    /// - Parameter selection: A selection manager that identifies the selected row(s).
    ///
    /// - See Also: `View.selectionValue` which gives an identifier to the rows.
    ///
    /// - Note: On iOS and tvOS, you must explicitly put the `List` into Edit
    /// Mode for the selection to apply.
    @available(watchOS, unavailable)
    public init(selection: Binding<Selection>?, content: () -> Content)

Pour ce faire, ajoutez un EditButton à votre vue quelque part. Après cela, il vous suffit de lier un var pour quelque chose qui implémente SelectionManager (vous n'avez pas besoin de lancer le vôtre ici: D)

var demoData = ["Phil Swanson", "Karen Gibbons", "Grant Kilman", "Wanda Green"]

struct SelectionDemo : View {
    @State var selectKeeper = Set<String>()

    var body: some View {
        NavigationView {
            List(demoData.identified(by: \.self), selection: $selectKeeper){ name in
                Text(name)
            }
            .navigationBarItems(trailing: EditButton())
            .navigationBarTitle(Text("Selection Demo \(selectKeeper.count)"))
        }
    }
}

Cette approche ressemble à ceci: enter image description here

Si vous ne souhaitez pas utiliser le "mode édition":

À ce stade, nous allons devoir lancer le nôtre. Remarque: cette implémentation a un bug qui signifie que seul le Text provoquera une sélection. Il est possible de le faire avec Button mais à cause du changement dans la Bêta 2 qui a supprimé borderlessButtonStyle() cela a l'air maladroit, et je n'ai pas encore trouvé de solution.

struct Person: Identifiable, Hashable {
    let id = UUID()
    let name: String
}

var demoData = [Person(name: "Phil Swanson"), Person(name: "Karen Gibbons"), Person(name: "Grant Kilman"), Person(name: "Wanda Green")]

struct SelectKeeper : SelectionManager{
    var selections = Set<UUID>()

    mutating func select(_ value: UUID) {
        selections.insert(value)
    }

    mutating func deselect(_ value: UUID) {
        selections.remove(value)
    }

    func isSelected(_ value: UUID) -> Bool {
        return selections.contains(value)
    }

    typealias SelectionValue = UUID

}

struct SelectionDemo : View {
    @State var selectKeeper = Set<UUID>()

    var body: some View {
        NavigationView {
            List(demoData) { person in
                SelectableRow(person: person, selectedItems: self.$selectKeeper)
            }
            .navigationBarTitle(Text("Selection Demo \(selectKeeper.count)"))
        }
    }
}

struct SelectableRow: View {
    var person: Person

    @Binding var selectedItems: Set<UUID>
    var isSelected: Bool {
        selectedItems.contains(person.id)
    }

    var body: some View {
        GeometryReader { geo in
            HStack {
                Text(self.person.name).frame(width: geo.size.width, height: geo.size.height, alignment: .leading)
            }.background(self.isSelected ? Color.gray : Color.clear)
            .tapAction {
                if self.isSelected {
                    self.selectedItems.remove(self.person.id)
                } else {
                    self.selectedItems.insert(self.person.id)
                }
            }
        }
    }
}

enter image description here

11
piebie

Plutôt que d'utiliser le mode d'édition, je mettrais simplement à jour la ligne sur la base du modèle et basculerais un booléen dans le modèle lorsque la ligne est tapée comme suggéré par https://stackoverflow.com/a/57023746/1271826 . Peut-être quelque chose comme:

struct MultipleSelectionRow<RowContent: SelectableRow>: View {
    var content: Binding<RowContent>

    var body: some View {
        Button(action: {
            self.content.value.isSelected.toggle()
        }) {
            HStack {
                Text(content.value.text)
                Spacer()
                Image(systemName: content.value.isSelected ? "checkmark.circle.fill" : "circle")
            }
        }
    }
}

protocol SelectableRow {
    var text: String { get }
    var isSelected: Bool { get set }
}

Ensuite, vous pouvez faire des choses comme:

struct Person: Hashable, Identifiable, SelectableRow {
    let id = UUID().uuidString
    let text: String
    var isSelected: Bool = false
}

struct ContentView : View {
    @State var people: [Person] = [
        Person(text: "Mo"),
        Person(text: "Larry"),
        Person(text: "Curly")
    ]

    var body: some View {
        List {
            ForEach($people.identified(by: \.id)) { person in
                MultipleSelectionRow(content: person)
            }
        }
    }
}

Rendement:

enter image description here

11
Rob

Mode édition

Comme mentionné dans une réponse précédente, vous pouvez l'ajouter en mode édition. Cela signifie que l'utilisateur devra appuyer sur le bouton d'édition à un moment donné pour sélectionner des lignes. Ceci est utile si vous souhaitez avoir un état d'affichage et un état d'édition pour votre liste.

var demoData = ["Phil Swanson", "Karen Gibbons", "Grant Kilman", "Wanda Green"]

struct SelectionDemo : View {
    @State var selectKeeper = Set<String>()

    var body: some View {
        NavigationView {
            List(demoData, id: \.self, selection: $selectKeeper){ name in
                Text(name)
            }
            .navigationBarItems(trailing: EditButton())
            .navigationBarTitle(Text("Selection Demo \(selectKeeper.count)"))
        }
    }
}

Mode d'édition constante

Vous pouvez également simplement garder le mode d'édition toujours activé. SwiftUI possède des modificateurs d'environnement, qui vous permettent de contrôler manuellement toutes les variables d'environnement. Dans ce cas, nous voulons contrôler la variable editMode.

var demoData = ["Phil Swanson", "Karen Gibbons", "Grant Kilman", "Wanda Green"]

struct SelectionDemo : View {
    @State var selectKeeper = Set<String>()

    var body: some View {
        NavigationView {
            List(demoData, id: \.self, selection: $selectKeeper){ name in
                Text(name)
            }
// the next line is the modifier
            .environment(\.editMode, .constant(EditMode.active))
            .navigationBarTitle(Text("Selection Demo \(selectKeeper.count)"))
        }
    }
}
8
Andre Carrera