L’idée de base de ForEach est de construire des objets graphiques à partir d’une description statique. Si les propriétés de ces objets peuvent changer, il faut définir leurs états dans un tableau.
J’ai fait une petite démo pour illustrer ça, avec 4 rectangles de couleur bleu. Il suffit d’en toucher un pour passer en rouge et vice-versa.
J’ai commencé par créer une structure pour mémoriser l’état de l’objet (la couleur), bleu par défaut.
struct DescriptionRectangle : Identifiable {
var id = UUID()
var couleur = Color.blue
}
ForEach a des exigences particulières pour cette structure. Elle doit être Identifiable, et avoir une variable id contenant un identifiant unique, pour ne pas être confondue avec d’autres données du même type. Heureusement, Swift a une fonction UUID() pour fabriquer un identifiant unique sur demande.
J’ai ensuite écrit un composant graphique ViewRectangle() fabriquant un rectangle à partir de la description.
struct ViewRectangle : View {
var description:DescriptionRectangle
var body: some View {
Rectangle()
.frame(width: 200, height: 100)
.foregroundColor(description.couleur)
}
}
Etape suivante : stocker l’état initial des objets dans une liste.
@State var listeDescriptions = [DescriptionRectangle(),
DescriptionRectangle(),
DescriptionRectangle(),
DescriptionRectangle()]
4 rectangles, donc 4 descriptions. Elles sont identiques puisque tous les rectangles sont bleus.
La boucle ForEach parcoure la liste pour créer les rectangles à partir de leurs description.
var body: some View {
VStack {
ForEach(0..<listeDescriptions.count) { index in
ViewRectangle(description: self.listeDescriptions[index])
.onTapGesture {
self.tapRectangle(index: index)
}
}
}
}
Chaque rectangle est associé avec une gesture Tap, liée avec une fonction de la structure View.
Chaque fois que l’utilisateur touche un Rectangle, la fonction tapRectangle() reçoit son numéro d’index (sa position dans le tableau de description).
Pour modifier la couleur d’un Rectangle, il suffit de modifier sa description. Puisque c’est un tableau de type @State, Swift détecte automatiquement le changement et avertit la vue qu’elle doit se redessiner.
La version de test se contente d’inverser la couleur du Rectangle (bleu=>Rouge et inversement).
func tapRectangle(index:Int) {
if listeDescriptions[index].couleur == Color.blue {
listeDescriptions[index].couleur = Color.red
} else {
listeDescriptions[index].couleur = Color.blue
}
}
Mais elle pourrais faire des choses plus complexes, comme avertir une classe Swift du contact, et lui demander quoi faire.
J’ai été surpris de voir que l’on peut se passer du tag en donnant directement le numéro d’index dans la closure. C’est pratique. Mais on peut toujours utiliser le .tag si c’est nécessaire (comme pour un tag alphanumérique).
Le code complet :
import SwiftUI
struct DescriptionRectangle : Identifiable {
var id = UUID()
var couleur = Color.blue
}
struct ViewRectangle : View {
var description:DescriptionRectangle
var body: some View {
Rectangle()
.frame(width: 200, height: 100)
.foregroundColor(description.couleur)
}
}
struct ContentView: View {
@State var listeDescriptions = [DescriptionRectangle(),
DescriptionRectangle(),
DescriptionRectangle(),
DescriptionRectangle()]
func tapRectangle(index:Int) {
if listeDescriptions[index].couleur == Color.blue {
listeDescriptions[index].couleur = Color.red
} else {
listeDescriptions[index].couleur = Color.blue
}
}
var body: some View {
VStack {
ForEach(0..<listeDescriptions.count) { index in
ViewRectangle(description: self.listeDescriptions[index])
.onTapGesture {
self.tapRectangle(index: index)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}