Nos dépenses sont maintenant sauvegardées dans Core Data. On va les récupérer pour les afficher dans notre liste.
Récupérez des données
Vous savez déjà récupérer des données, on l'a fait avec Person
précédemment dans notre PeopleRepository
. On va donc faire exactement la même chose, cette fois-ci directement dans notre classe SpendingRepository
.
Créons donc une méthode getSpendings
dans notre repository :
// MARK: - Repository
func getSpendings(completion: ([Spending]) -> Void) {
let request: NSFetchRequest<Spending> = Spending.fetchRequest()
do {
let spendings = try coreDataStack.viewContext.fetch(request)
completion(spendings)
} catch {
completion([])
}
}
C'est exactement le même code que tout à l'heure pour la classe PeopleRepository
: on récupère la liste des Spendings
enregistrés dans CoreData, et on les retourne dans un tableau grâce à une closure
.
Affichez des données
On n'a plus qu'à afficher les données dans la liste. Pour ça, on va commencer par récupérer les données dans une propriété spendings
:
final class ListViewController: UIViewController {
// MARK: - Properties
private let spendingsRepository = SpendingsRepository()
private let settingsRepository = SettingsRepository()
private var spendings: [Spending] = []
(....)
}
… que l’on va remplir au moment où la vue s’affichera (donc à viewDidLoad
) :
// MARK: - View life cycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
spendingsRepository.getSpendings(completion: { [weak self] spendings in
self?.spendings = spendings
self?.tableView.reloadData()
})
}
Nous allons ensuite utiliser spendings
pour remplir notre Table View :
extension ListViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return spendings.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SpendingCell", for: indexPath)
let spending = spendings[indexPath.row]
cell.textLabel?.text = spending.content
cell.detailTextLabel?.text = "\(spending.amount) \(settingsRepository.currency)"
return cell
}
}
Notre Table View affiche maintenant les données issues de Core Data ! Je suis fier de vous!
Organisez les données
Tout ça, c'est très bien... mais on peut faire mieux ! Notre liste n'est pas du tout organisée. Tout est mélangé, il n'y a pas d'ordre. Ce serait quand même beaucoup mieux si on pouvait organiser nos dépenses par participant et par montant.
L'objectif est le suivant :
On va créer des sections qui seront organisées par participant. Et à l'intérieur, les dépenses vont être rangées par ordre croissant.
Pour pouvoir organiser la liste ainsi, il faut préalablement que les données le soient. Pour récupérer des données dans un ordre particulier, il faut utiliser la propriété sortDescriptors
de NSFetchRequest
.
Il faut fournir à sortDescriptors
un tableau de NSSortDescriptor
. NSSortDescriptor
permet de préciser la clé avec laquelle on souhaite ranger les données. On l'utilise comme ceci :
NSSortDescriptor(key: "amount", ascending: true),
Ici, je demande de trier les données selon le montant des dépenses par ordre croissant. Je peux aussi fournir comme clé l'attribut d'une autre entité à laquelle je suis relié en utilisant le point :
NSSortDescriptor(key: "person.name", ascending: true),
Ici, je demande de trier dans l'ordre des participants, en utilisant leur nom.
Je peux enfin utiliser la nouvelle forme disponible avec les keyPath
. Ce concept nous permet à présent d'atteindre une propriété existante sans avoir à écrire l’élément voulu dans une chaîne de caractères. C’est très pratique, car ça nous évite simplement une éventuelle faute dans la chaîne :
NSSortDescriptor(keyPath: \Spending.amount, ascending: true)
Maintenant, si je mets tout ça ensemble, ça donne :
func getSpendings(completion: ([Spending]) -> Void) {
let request: NSFetchRequest<Spending> = Spending.fetchRequest()
request.sortDescriptors = [
NSSortDescriptor(keyPath: \Spending.person?.name, ascending: true),
NSSortDescriptor(keyPath: \Spending.amount, ascending: true)
]
do {
let spendings = try coreDataStack.viewContext.fetch(request)
completion(spendings)
} catch {
completion([])
}
}
Ici je donne deux NSSortDescriptor
à ma requête. Cela signifie que je veux d'abord que les données soient ordonnées par participant. Et ensuite pour chaque participant, je souhaite obtenir les données rangées par montant croissant.
Organisez la liste
Comme je vais utiliser des sections dans ma liste pour afficher mes données, ce serait pratique si mes données étaient aussi organisées selon des sections. Pour cela, on utilise un tableau de tableaux. En gros, le premier niveau contient les sections, et le deuxième les cellules.
Pour l'instant, on a un tableau de Spending
, donc l'objectif est d'en faire un tableau de tableaux de Spending
:
On va donc réorganiser les données à l'intérieur du tableau pour les mettre dans des tableaux par participant.
Pour cela, je vais changer la logique montrée au-dessus :
Je vais récupérer dans une
fetchRequest
la liste des personnes, rangée par ordre alphabétique.Puis, je vais créer un tableau des tableaux avec la liste des dépenses par personne.
Voici tout d’abord la fonction permettant de retourner un tableau de tableaux de dépenses à partir d’un tableau de personnes. Je vous donne le code pour faire cela, ce n'est pas le plus important. Vous pouvez copier ceci dans votre fichier SpendingsRepository
:
private extension Array where Element == Person {
var spendingsByPerson: [[Spending]] {
return self.compactMap {
($0.spendings?.allObjects as? [Spending])?.sorted(by: {$0.amount < $1.amount} )
}
}
}
Ensuite, je change la logique de notre fonction getSpendings
afin de récupérer en premier lieu la liste des personnes, rangée par ordre alphabétique, puis j’utilise la propriété spendingsByPerson
que l’on vient de créer dans l’extension pour transformer la liste :
func getSpendings(completion: ([[Spending]]) -> Void) {
let request: NSFetchRequest<Person> = Person.fetchRequest()
request.sortDescriptors = [
NSSortDescriptor(keyPath: \Person.name, ascending: true),
]
do {
let spendings = try coreDataStack.viewContext.fetch(request)
completion(spendings.spendingsByPerson)
} catch {
completion([])
}
}
Maintenant, on peut modifier notre Table View pour prendre en compte cette nouvelle organisation de nos données :
extension ListViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return spendings.count // ⇐==
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return spendings[section].count // ⇐==
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SpendingCell", for: indexPath)
let spending = spendings[indexPath.section][indexPath.row] // ⇐==
cell.textLabel?.text = spending.content
cell.detailTextLabel?.text = "\(spending.amount) \(settingsRepository.currency)"
return cell
}
}
Ensuite, on peut modifier le titre de nos sections avec la méthode titleForSection
de UITableViewDataSource
.
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard let person = spendings[section].first?.person else { return nil }
var totalAmount = 0.0
for spending in spendings[section] {
totalAmount += spending.amount
}
return "\(person.name ?? "🧙♂️") \(totalAmount) \(settingsRepository.currency)"
}
Et voilà ! Vous pouvez tester, et vos données sont maintenant bien organisées !
En résumé
On peut utiliser la propriété
sortDescriptors
deNSFetchRequest
pour ordonner les données.On peut lui passer plusieurs
NSSortDescriptor
pour un tri sur plusieurs niveaux.
C'est tout pour notre démo ! On a fini notre application Cekikapeye ! Bravo ! Dans ce cours, je n'ai eu le temps de vous faire qu'une introduction à Core Data. Mais c'est une technologie très riche et très puissante avec plein de fonctionnalités. Donc j'ai décidé dans le prochain chapitre de vous donner plusieurs aperçus de ses nombreuses possibilités.