Formation PUB900 : Développer une application pour iPhone avec SwiftUI, H-2024 Les listes

32.1 List


Avec SwiftUI, la vue List permet d’afficher des éléments les uns sous les autres et, selon les options choisies, de permettre leur sélection.

Elle gère automatiquement le défilement lorsque le contenu dépasse la hauteur de l’écran.

Dans cette fiche :

Protocole Identifiable

Bien que ce ne soit pas obligatoire, la liste est plus facile à créer à partir d'un tableau dont les éléments répondent au protocole Identifiable.

Swift

import SwiftUI

struct Item: Identifiable {
  var id: Int
  var code: String
  var titre: String
  var couleur: Color
}

Au besoin, la structure pourra comprendre un identifiant autogénéré à l'aide de UUID().

Swift

struct Item: Identifiable {
  var id: UUID = UUID()
  var code: String
  var titre: String
  var couleur: Color
}

Il est désormais possible d'utiliser un tableau de Item dans une liste :

SwiftUI

struct ContentView: View {
  private var items: [Item] = [
    Item(code: "abc", titre: "Item 1", couleur: .blue),
    Item(code: "def", titre: "Item 2", couleur: .red),
    Item(code: "ghi", titre: "Item 3", couleur: .green),
  ]

  var body: some View {
    List(items) {
      Text($0.titre)
    }
  }
}

Si vous préférez travailler avec un nom de variable plutôt qu'avec $0 :

SwiftUI

List(items) { item in
  Text(item.titre)
}

List simple

Masquer le fond gris

On voit que par défaut, le fond de l'écran, derrière la liste, apparaît en gris. Cette apparence peut être changée à l'aide du modifieur scrollContentBackground().

SwiftUI

List(items) { item in
  Text(item.titre)
}
.scrollContentBackground(.hidden)

scrollContentBackground

Si les éléments ne répondent pas au protocole Identifiable

Dans le cas où les éléments de la liste ne répondent pas au protocole Identifiable, par exemple une liste de String, il faudra spécifier ce qui identifie chaque élément de façon unique.

Dans le cas le plus simple, on identifiera chaque élément à l'aide de .self. Ceci fonctionnera à condition que l'élément réponde au protocole Hashable. C'est le cas, par exemple, pour les entiers et les chaînes.

En plus d'être Hashable, les éléments devront être uniques sans quoi, SwiftUI aura un comportement erratique puisqu'il ne pourra pas identifier chaque élément.

SwiftUI

struct ContentView: View {
  private var donnees: [String] = ["a", "b", "c", "d", "e", "f", "g"]

  var body: some View {
    List(donnees, id: \.self) { donnee in
      Text(donnee)
    }
  }
}

Dans le cas d'une structure, si chaque propriété est un entier ou une chaîne, il suffit d'ajouter : Hashable à la déclaration de la structure.

Voici un exemple avec un tableau d'items :

SwiftUI

// la structure se conforme au protocole Hashable sans rien lui ajouter de plus puisque toutes ses propriétés
// se conforment également au protocole Hashable
struct Jeu: Hashable {
  var nom: String
  var niveau: Int
}

SwiftUI

struct ContentView: View {
  private var jeux: [Jeu] = [
    Jeu(nom: "Super Mario Bros.", niveau: 2),
    Jeu(nom: "The Legend of Zelda", niveau: 3),
    Jeu(nom: "Pac-Man", niveau: 2),
    Jeu(nom: "Tetris", niveau: 1)
  ]

  var body: some View {
    List(jeux, id: \.self) { jeu in
      Text(jeu.nom)
    }
  }
}

Dans des cas plus complexes, vous pouvez indiquer clairement quelle propriété permettra d'identifier l'élément de façon unique.

Par exemple, si on a une structure Etudiant qui contient une propriété code unique :

SwiftUI

struct Etudiant {
  var code: String   // le code devra être unique dans le tableau d'étudiants
  var prenom: String
  var nom: String
}

SwiftUI

struct ContentView: View {
  private var etudiants: [Etudiant] = [...]

  var body: some View {
    List(etudiants, id: \.code) { etudiant in
      Text(etudiant.prenom)
    }
  }
}

Affichage sophistiqué

Il est possible d'afficher l'information sous la forme qui vous convient.

SwiftUI

List(items) { item in
  HStack {
    Image(systemName: "paperclip")
    Text(item.code)
      .foregroundColor(item.couleur)
    Text("-")
    Text(item.titre)
      .foregroundColor(item.couleur)
  }
}

List

Avec ForEach

Il est possible de combiner List et ForEach afin de bénéficier de tous les aspects gérées automatiquement par List comme le défilement, le style natif ou encore la possibilité de sélection,  tout en bénéficiant de plus de souplesse pour générer l'affichage de chacun des éléments de la liste.

Voici un exemple qui permet d'afficher des sous-catégories :

Swift

private var itemsCategorie1: [Item] = [
  Item(code: "abc", titre: "Item 1", couleur: .blue),
  Item(code: "ghi", titre: "Item 3", couleur: .green),
]

private var itemsCategorie2: [Item] = [
  Item(code: "def", titre: "Item 2", couleur: .red),
  Item(code: "jkl", titre: "Item 4", couleur: .purple),
  Item(code: "mno", titre: "Item 5", couleur: .orange),
]
...


List {
  Section(header: Label("Catégorie 1", systemImage: "seal")) {
    ForEach(itemsCategorie1) { item in
      HStack {
        Text(item.code)
          .foregroundColor(item.couleur)
        Text("-")
        Text(item.titre)
          .foregroundColor(item.couleur)
      }
    }
  }

  Section(header: Label("Catégorie 2", systemImage: "cone")) {
    ForEach(itemsCategorie2) { item in
      HStack {
        Text(item.code)
          .foregroundColor(item.couleur)
        Text("-")
        Text(item.titre)
          .foregroundColor(item.couleur)
      }
    }
  }
}

List avec Foreach

Sélectionner un élément

La vue List est capable de gérer la sélection d'un élément.

Ici, ID doit être en lettres majuscules car il représente le type de données du paramètre id de la structure qui répond au protocole Identifiable.

SwiftUI

struct ContentView: View {
  private var items: [Item] = [
    Item(code: "abc", titre: "Item 1", couleur: .blue),
    Item(code: "def", titre: "Item 2", couleur: .red),
    Item(code: "ghi", titre: "Item 3", couleur: .green),
  ]
  @State private var selection: Item.ID?

  var body: some View {
    VStack {
      List(items, selection: $selection) { item in
        Text(item.titre)
      }

      if let selection,
        let itemSelectionne = items.first(where: { $0.id == selection }) {
        Text(itemSelectionne.titre)
      }
      else {
        Text("Aucune sélection")
      }
    }
  }
}

Liste avec sélection

Sélectionner plusieurs éléments

La sélection multiple est possible seulement si la liste est placée en mode édition.

SwiftUI

struct ContentView: View {
  private var items: [Item] = [
    Item(code: "abc", titre: "Item 1", couleur: .blue),
    Item(code: "def", titre: "Item 2", couleur: .red),
    Item(code: "ghi", titre: "Item 3", couleur: .green),
  ]
  @State private var selections: Set<Item.ID> = []

  var body: some View {
    VStack {
      List(items, selection: $selections) { item in
        Text(item.titre)
      }
      .environment(\.editMode, .constant(.active))


      Text("\(selections.count) items sélectionnés")
    }
  }
}

Sélection multiple

Changer l'apparence de l'item sélectionné

Le modifieur listRowBackground() permet de mieux contrôler l'apparence de l'item sélectionné.

SwiftUI

@State private var selection: Item.ID?
...
List(items, selection: $selection) { item in
  Text(item.titre)
    .listRowBackground(item.id == selection
      ? Color.gray.opacity(0.1)
      : nil
    )
}

List avec élément sélectionné visible

Dans le cas où le contenu de la liste n'est pas généré directement par List, il faudra ajouter un .tag() pour permettre à List de reconnaître les éléments lors de la sélection.

SwiftUI

List(selection: $selection) {
  ForEach(items) { item in
    HStack {
      Image(systemName: "paperclip")
      Text(item.code)
        .foregroundColor(item.couleur)
      Text("-")
      Text(item.titre)
        .foregroundColor(item.couleur)
    }
    .tag(item.id)
    .listRowBackground(item.id == selection
      ? Color.gray.opacity(0.1)
      : nil
    )
  }
}

List avec élément sélecitonné visible

Placer deux boutons sur une même ligne du List

Par défaut, dans une liste, SwiftUI étire la zone cliquable des boutons sur toute la largeur de l'écran. Un seul bouton par ligne est donc cliquable.

Ce comportement est pratique : il permet de cliquer n'importe où sur la ligne pour réaliser l'action du bouton.

Si vous avez besoin de plus d'un bouton par ligne, il faut limiter la taille des boutons à l'aide d'un style prédéfini (.buttonStyle()).

SwiftUI

struct LignePanier: Identifiable {
  var id: UUID = UUID()
  var produit: String
  var quantite: Int = 0
}

SwiftUI

struct ContentView: View {
  @State private var lignesPanier: [LignePanier] = [
    LignePanier(produit: "Produit 1"),
    LignePanier(produit: "Produit 2"),
    LignePanier(produit: "Produit 3"),
  ]

  var body: some View {
    List($lignesPanier){ $lignePanier in
      VStack {
        HStack {
          Text(lignePanier.produit)
          Spacer()
          Text("Quantité : \(lignePanier.quantite)")
        }

        HStack {
          Button(action: {
            lignePanier.quantite = lignePanier.quantite - 1
          }) {
            Text("-")
              .frame(maxWidth: .infinity)
              .padding()
              .foregroundColor(Color.primary)
              .background(Color.secondary .opacity(0.4))
              .cornerRadius(15.0)
          }
          .buttonStyle(.borderless)   // nécessaire avec deux boutons sur la même ligne


          Spacer().frame(width: 20)

          Button(action: {
           lignePanier.quantite = lignePanier.quantite + 1
          }) {
            Text("+")
              .frame(maxWidth: .infinity)
              .padding()
              .foregroundColor(Color.primary)
              .background(Color.secondary .opacity(0.4))
              .cornerRadius(15.0)
          }
          .buttonStyle(.borderless)    // nécessaire avec deux boutons sur la même ligne
        }
      }
    }
  }
}

Le visuel est très sommaire mais il permet d'illustrer l'importance de .buttonStyle() pour placer deux boutons côte-à-côte sur une ligne de la liste.

Deux boutons de large dans List

Pour plus d'information

« SwiftUI - Dynamic List & Identifiable ». Medium. https://medium.com/flawless-app-stories/swiftui-dynamic-list-identifiable-73c56215f9ff

« What is the difference between List and ForEach in SwiftUI? ». Stack Overflow. https://stackoverflow.com/questions/56535326/what-is-the-difference-between-list-and-foreach-in-swiftui

« Why does \.self work for ForEach? ». Hacking with Swift. https://www.hackingwithswift.com/books/ios-swiftui/why-does-self-work-for-foreach

« How to conform to the Hashable protocol ». Hacking with Swift. https://www.hackingwithswift.com/example-code/language/how-to-conform-to-the-hashable-protocol

▼Publicité

Veuillez noter que le contenu de cette fiche vous est partagé à titre gracieux, au meilleur de mes connaissances et sans aucune garantie.
Merci de partager !
Soumettre