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 :
Je vous présente ici des techniques qui permettent de lister les éléments d'une table enfant dans un ordre donné.
La technique à privilégier consiste à stocker une version triée dans une propriété calculée dans la classe de la table parent.
@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.
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)
}
...
}
}
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)")
}
}
...
}
}
}
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é.
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)
}
...
}
}
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)")
}
}
...
}
}
}
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.
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
})
}
}
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.
@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 :
List(categories) { categorie in
HStack {
...
Text(categorie.itemLePlusCher != nil ? String(categorie.itemLePlusCher!.prix) : "-")
}
}
▼Publicité