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

37.10 Trier les données d'une table enfant


Lors d'une requête SwiftData qui implique une table parent et une table enfant, par exemple un tableau de catégories dont chaque catégorie contient un tableau d'items, il n'est pas possible de demander à SwiftData de trier les données de la table enfant directement dans la requête.

Du moins, ce n'était pas possible au moment d'écrire ces lignes.

Il existe pourtant des techniques qui permettent de contourner ce problème.

La technique à utiliser sera différente selon la nature de ce qui doit être réalisé avec la table enfant.

Dans cette fiche :

Lister les éléments de la table enfant

Je vous présente ici des techniques qui permettent de lister les éléments d'une table enfant dans un ordre donné.

Propriété calculée

La technique à privilégier consiste à stocker une version triée dans une propriété calculée dans la classe de la table parent.

Fichier Models/Categorie.swift

@Model
final class Categorie {
  var titre: String
  var items: [Item] = []

  ...

  /// Retourne les items triés par code.
  var itemsTries: [Item] {
    return items.sorted(by: { $0.code < $1.code })
  }
}

La liste triée est immédiatement disponible.

Dans cet exemple, j'ai utilisé NavigationLink avec .navigationDestination pour afficher les items d'une catégorie sur une autre page.

Fichier ContentView.swift

struct ContentView: View {
  @Query(sort: \Categorie.titre) var categories: [Categorie]

  var body: some View {
    ...
    List (categories) { categorie in
      NavigationLink(value: categorie) {
        Text(categorie.titre)
      }
    }
    .navigationDestination(for: Categorie.self) { categorie in
      ListeItems(categorie: categorie)
    }

    ...
  }
}

Fichier ListeItems.swift

struct ListeItems: View {
  var categorie: Categorie

  var body: some View {
    VStack {
      if !categorie.items.isEmpty {

        List (categorie.itemsTries) { item in
          Text("\(item.code) - \(item.titre)")
        }
      }
      ...
    }
  }
}

.sorted()

Une autre technique consiste à trier les données de la table enfant à l'aide de .sorted() au moment de les afficher.

Mais attention : en faisant .sorted() dans le body, le tri sera refait à chaque recalcul de la vue. Ceci pourrait nuire aux performances quand le nombre d'éléments à trier est élevé.

Fichier ContentView.swift

struct ContentView: View {
  @Query(sort: \Categorie.titre) var categories: [Categorie]

  var body: some View {
    ...
    List (categories) { categorie in
      NavigationLink(value: categorie) {
        Text(categorie.titre)
      }
    }
    .navigationDestination(for: Categorie.self) { categorie in
      ListeItems(categorie: categorie)
    }

    ...
  }
}

Fichier ListeItems.swift

struct ListeItems: View {
  var categorie: Categorie

  var body: some View {
    VStack {
      if !categorie.items.isEmpty {

        List (categorie.items.sorted(by: {$0.code < $1.code})) { item in
          Text("\(item.code) - \(item.titre)")
        }
      }
      ...
    }
  }
}

Travailler avec une autre variable triée

Cette technique est un peu plus complexe mais elle fonctionne tout de même très bien.

Elle consiste à trier la liste dans le .onAppear() et à stocker le résultat dans une autre variable.

Attention : il faudra mettre cette autre variable à jour manuellement lorsque les données de la base de données seront modifiées.

Fichier ListeItems.swift

struct ListeItems: View {
  var categorie: Categorie
  @State private var itemsTries: [Item] = []

  var body: some View {
    VStack {
      if !categorie.items.isEmpty {

        List (itemsTries) { item in
          Text("\(item.code) - \(item.titre)")
        }
      }
      ...
      Button(action: {
        ...   // opération qui modifie la liste d'items
        )
        remplirItemsTries()   // sans ceci, les changements n'apparaitraient pas dans la vue
      }) {
        Text("Faire quelque chose")
      }
    }
    .onAppear() {
      remplirItemsTries()
    }

  }

  /// réordonne les items de la catégorie
  func remplirItemsTries() {
    itemsTries = categorie.items.sorted(by: { instance1, instance2 in
      instance1.code < instance2.code
    })
  }
}

Obtenir la première ou la dernière valeur d'une table enfant

Prenons le cas où une application doit retrouver le premier ou le dernier enregistrement d'une table enfant selon un tri donné. Dans ce cas, elle n'a pas besoin de boucler dans tous les enregistrements d'une table enfant. La technique à utiliser sera alors différente.

Je vous propose une solution qui consiste à ajouter une propriété calculée dans la classe de la table parent.

Fichier Models/Categorie.swift

@Model
final class Categorie {
  var titre: String
  var items: [Item] = []

  ...

  /// L'item dont le prix est le plus élevé dans cette catégorie.
  /// Retourne nil si la liste d'items est vide.
  var itemLePlusCher: Item? {
   items.sorted(by: { $0.prix > $1.prix }).first
  }
}

Pour utiliser cette méthode :

SwiftUI

List(categories) { categorie in
  HStack {
    ...

    Text(categorie.itemLePlusCher != nil ? String(categorie.itemLePlusCher!.prix) : "-")

  }
}

 

▼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