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 :
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.
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().
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 :
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 :
List(items) { item in
Text(item.titre)
}

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().
List(items) { item in
Text(item.titre)
}
.scrollContentBackground(.hidden)

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.
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 :
// 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
}
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 :
struct Etudiant {
var code: String // le code devra être unique dans le tableau d'étudiants
var prenom: String
var nom: String
}
struct ContentView: View {
private var etudiants: [Etudiant] = [...]
var body: some View {
List(etudiants, id: \.code) { etudiant in
Text(etudiant.prenom)
}
}
}
Il est possible d'afficher l'information sous la forme qui vous convient.
List(items) { item in
HStack {
Image(systemName: "paperclip")
Text(item.code)
.foregroundColor(item.couleur)
Text("-")
Text(item.titre)
.foregroundColor(item.couleur)
}
}

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

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.
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")
}
}
}
}

La sélection multiple est possible seulement si la liste est placée en mode édition.
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")
}
}
}

Le modifieur listRowBackground() permet de mieux contrôler l'apparence de l'item sélectionné.
@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
)
}

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

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()).
struct LignePanier: Identifiable {
var id: UUID = UUID()
var produit: String
var quantite: Int = 0
}
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.

« 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é