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é.
Selon ce que vous voulez, il y a deux façons de procéder:
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:
À 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)
}
}
}
}
}
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")
}
}
}
}
Où
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:
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)"))
}
}
}