Apprendre à utiliser Core Data en programmation Swift

Partie 2

Ce tutoriel est compatible avec Xcode 6.3, mise à jour le 16 février 2015.

Ce tutoriel est la deuxième partie d'une série de cours pour vous apprendre à utiliser le Core Data en programmation Swift pour mettre en place des applications iOS persistantes. Si vous n'avez pas encore lu la première partie, lisez-la d'abord.

Vous pouvez également avoir un premier accès à mon livre Swift disponible ici

1 commentaire Donner une note à l'article (5)

Article lu   fois.

Les deux auteur et traducteur

Site personnel

Traducteur : Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Créer plus d'enregistrements

Avant tout, pour faire quelque chose d'aussi simple qu'afficher une fenêtre présentant une alerte, nous aurons besoin de générer quelques enregistrements de plus pour faire des manipulations. Ouvrons notre fichier LogItem.swift et ajoutons une fonction d'aide pour ajouter de nouveaux enregistrements.

De manière basique, nous voulons être capables d' appeler aisément une méthode dans la classe LogItem, lui passer quelques paramètres, et obtenir un nouveau LogItem en retour.

 
Sélectionnez
let newItem = LogItem.createInManagedObjectContext(managedObjectContext, "Item Title", "Item text")

La classe LogItem n'est pas liée à un quelconque NSManagementObjectContext, donc nous voulons être sûrs que nous ne sommes pas en train de stocker la référence au contexte d'objet géré où que ce soit dans le modèle ; il faut passer par là quand nous voulons créer un objet semblable.

D'accord ! Réalisons donc une méthode dans LogItem.swift :

LogItem.swift
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
class LogItem: NSManagedObject {
 
    @NSManaged var itemText: String
    @NSManaged var title: String
 
    class func createInManagedObjectContext(moc: NSManagedObjectContext, title: String, text: String) -> LogItem {
        let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: moc) as! LogItem
        newItem.title = title
        newItem.itemText = text
         
        return newItem
    }
     
}

La première ligne est la définition de la fonction. Il s'agit d'une fonction de classe, appelée createInModelObjectContext qui prend en argument un objet de type NSManagedObjectContext nommé moc, une chaîne de caractères appelée title, et une autre chaîne de caractères appelée text. La fonction retourne un objet LogItem qui a été inséré dans le context spécifié d'objet géré.

Ensuite, elle exécute un code quasiment identique au précédent, pour créer un nouvel objet LogItem, sauf que maintenant, elle utilise les arguments qui lui sont passés afin de paramétrer le nouvel objet LogItem.

Nous pouvons maintenant remplacer notre code original dans viewController.swift par la nouvelle méthode. Ajoutons un lot de nouveaux objets…

ViewController.swift
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
override func viewDidLoad() {
    super.viewDidLoad()
    //  Effectuer une configuration supplémentaire après le chargement de la vue, généralement à partir d'un nib.
     
    //  Utiliser option obligatoire pour confirmer le managedObjectContext
    if let moc = self.managedObjectContext {
         
        // Créer des données fictives pour travailler avec
        var items = [
            ("Best Animal", "Dog"),
            ("Best Language","Swift"),
            ("Worst Animal","Cthulu"),
            ("Worst Language","LOLCODE")
        ]
         
        //  Boucle pour créer des items
        for (itemTitle, itemText) in items {
            // Créer un item
            LogItem.createInManagedObjectContext(moc,
                title: itemTitle, text: itemText)
        }
    }
}

Pour garder la simplicité du code, nous utilisons quelques raccourcis afin de fournir nos données. Ce que vous voyez lorsque j'organise les objets dans un tableau (en utilisant des crochets []), c'est que chaque enregistrement est un doublet de chaînes de caractères [(String,String)].

Ensuite, je les redécompose en deux variables, titleArticle et textArticle pour chacun des doublets du tableau.

Finalement, j'appelle la méthode creat, que nous avions créée plus tôt, en lui passant les nouveaux itemTitle et itemText.

Si vous savez déjà comment mettre en place un UITableView et que vous voulez passer au Core Data, cliquez ici.

Maintenant que nous avons une paire d'enregistrements, enlevons presentItemInfo et à la place, optons pour une vue tabulaire ici. Nous allons l'ajouter complètement à droite, sous viewDidLoad et par programmation nous allons créer UITableView. Dans mon didacticiel iTunes, nous le faisons en utilisant des tableaux de bord historiques. Si vous êtes intéressés à travailler avec ces composants, je vous recommande de prendre un délai pour y lire comment obtenir ce réglage.

Nous allons paramétrer la vue en ajoutant une logTableView à la classe ViewController, et mettre le tout dans la fonction viewDidLoad().

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
// Créer une vue dès que cette classe est chargée
var logTableView = UITableView(frame: CGRectZero, style: .Plain)
 
override func viewDidLoad() {
    super.viewDidLoad()
    // Effectuer une configuration supplémentaire après le chargement de la vue, généralement à partir d'un nib.
     
    // Utiliser l'option obligatoire pour confirmer le managedObjectContext

    if let moc = self.managedObjectContext {
         
        //  Créer des données fictives pour travailler avec
        var items = [
            ("Best Animal", "Dog"),
            ("Best Language","Swift"),
            ("Worst Animal","Cthulu"),
            ("Worst Language","LOLCODE")
        ]
         
        // Boucle pour créer des items
        for (itemTitle, itemText) in items {
            // Créer un item
            LogItem.createInManagedObjectContext(moc,
                title: itemTitle, text: itemText)
        }
         
         
        // Maintenant que la vue est chargée, nous avons un cadre pour la vue, qui sera (0,0, largeur de l'écran, la hauteur de l'écran)
        // C'est une bonne taille pour l'affichage du tableau, donc on l'utilisera
        //Le seul réglage que nous allons faire est de le déplacer de 20 pixels vers le bas et de le réduire de 20 pixels
        //  afin d'apercevoir la barre d'état
         
        // Stocker le cadre dans une variable temporaire
        var viewFrame = self.view.frame
         
        // Le régler à 20 points
        viewFrame.origin.y += 20
         
        // Réduire la hauteur totale de 20 points
        viewFrame.size.height -= 20
         
        // Régler le cadre de logTableview pour égaler notre variable temporaire avec la taille de la vue
        // Ajuster en tenant compte de la taille de la barre d'états
        logTableView.frame = viewFrame
         
        // Ajouter la vue de la table à ce controlleur de vue
        self.view.addSubview(logTableView)
         
        // Ici nous disons à la vue que nous comptions utiliser une cellule que nous appellerons « LogCell »
        // Pour l'instant, elle sera associée à la classe UITableViewCell
        logTableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "LogCell")
         
        // Ceci indique à la vue qu'il devrait obtenir ses données de cette classe, ViewController
        logTableView.dataSource = self
         
    }
}

Depuis que nous avions paramétré le dataSource pour être notre classe viewController, nous avons alors besoin d'adhérer au protocole UITableViewDataSource, donc d'ajouter cela à la définition de la classe de ViewController :

 
Sélectionnez
1.
class ViewController: UIViewController, UITableViewDataSource {

…et d'ajouter les méthodes actuelles de dataSource

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
// MARK: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 5
}
 
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("LogCell") as! UITableViewCell
    cell.textLabel?.text = "\(indexPath.row)"
    return cell
}

Toujours avec moi ?

Je l'espère… sinon, voici le code complet de ViewController.swift jusqu'à maintenant. Notez que nous avons aussi enlevé la fonction viewDidAppear puisque nous l'utilisions uniquement pour un test auparavant.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
import UIKit
import CoreData
 
class ViewController: UIViewController, UITableViewDataSource {
     
    // Récupérer le managedObjectContext de AppDelegate
    let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
 
    // Créer la vue dès que cette classe est chargée
    var logTableView = UITableView(frame: CGRectZero, style: .Plain)
 
    override func viewDidLoad() {
        super.viewDidLoad()

        //   Effectuer une configuration supplémentaireaprès le chargement de la vue, généralement à partir de nib
         
        // Utiliser l'option obligatoire pour confirmer le managedObjectContext
        if let moc = self.managedObjectContext {
             
            // Créer des données virtuelles pour utilisation
            var items = [
                ("Best Animal", "Dog"),
                ("Best Language","Swift"),
                ("Worst Animal","Cthulu"),
                ("Worst Language","LOLCODE")
            ]
             
            // Boucle pour créer les items
            for (itemTitle, itemText) in items {
                // Créer un item
                LogItem.createInManagedObjectContext(moc,
                    title: itemTitle, text: itemText)
            }
             
             
            // Maintenant que la vue est chargée, nous avons un cadre pour la vue, qui sera (0,0, largeur de l'écran, la hauteur de l'écran)
            //  C'est une bonne taille pour l'affichage du tableau, donc nous l'utilisons
            // Le seul réglage que nous allons faire est de le déplacer de 20 pixels vers le bas et de le réduire de 20 pixels
            // afin d'apercevoir la barre d'état
            // Stocker le cadre dans une variable temporaire
            var viewFrame = self.view.frame

            // Ajuster à 20 points
            viewFrame.origin.y += 20
             
            // Réduire la hauteur totale à 20 points
            viewFrame.size.height -= 20
             
            // Régler le cadre logTableview pour égaler  la taille notre variable temporaire 
            // Ajuster en tenant compte de la hauteur de la barre d 'états
            logTableView.frame = viewFrame
             
            // Ajouter la vue à ce controlleur de vue
            self.view.addSubview(logTableView)
             
            // Ici nous disons à la vue que nous utiliserons une cellule que nous appellerons « LogCell »
            // Elle sera associée pour l'instant à la classe standard UITableViewCell
            logTableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "LogCell")
             
            // Ceci indique  à la vue qu'elle devrait obtenir ses données de cette classe, ViewController
            logTableView.dataSource = self
             
        }
    }
     
    // MARK: UITableViewDataSource
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }
     
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("LogCell") as! UITableViewCell
        cell.textLabel?.text = "\(indexPath.row)"
        return cell
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Éliminer toutes les ressources qui peuvent être recréées
    }
 
}

Cela va nous donner une liste numérotée si nous exécutons l'application. Cela confirme seulement que la vue est correctement paramétrée.

Si jamais vous obtenez une erreur semblable à : «  The model used to open the store is incompatible with the one used to create the store » cela signifie que le modèle avec lequel le Core Data travaille, ne correspond pas assez avec la base de données.

Résoudre ce problème avec une application en ligne signifierait migrer entre les versions de votre modèle. Mais pour le fait d'apprendre et de faire le développement initial, habituellement la manière la plus rapide de le faire est de supprimer l'application du simulateur ; en effaçant les données, et résolvant ainsi le conflit.

Image non disponible

Maintenant que tout est paramétré et prêt à être exécuté, nous pouvons commencer à travailler afin d'obtenir un affichage de nos données de journalisation dans la vue.

Veuillez, je vous prie, noter qu'à ce stade du tutoriel j'évite intentionnellement la classe NSFetchResultsController. J'agis ainsi parce que je crois donner de cette façon plus de sens, à la perspective du core data, dans cette première approche que vous voyez, faite à la vieille mode. Une fois que vous aurez achevé ce tutoriel, je vous encouragerai à jeter un coup d'œil sur la classe afin de voir comment vous auriez pu, à la place, l'utiliser pour réaliser quelques-unes de ces choses. Je pense que c'est important d'apprendre d'abord comment réaliser une vue renforcée par le Core data, sans utiliser la fonction d'assistance. La fonction d'assistance n'est pas applicable dans toutes les situations, et l'utiliser en premier ne vous aurait rendu aucun service. Vous verrez qu'elle ne fonctionne pas dans tous les cas, et quand elle fonctionne, elle cache certaines des choses.

.Maintenant, nous allons exécuter un « fetch » sur tous les résultats du Core Data dans viewDidLoad(), et les stocker dans un tableau de LogItems, « logItems ».:

Premièrement, nous allons ajouter la variable à la classe ViewController :

 
Sélectionnez
1.
2.
// Créer un tableau vide de LogItem
var logItems = [LogItem]()

Ensuite, nous allons la peupler à partir de viewDidLoad(), dans une certaine fonction, appelée fetchLog().

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
override func viewDidLoad() {
    ...
    fetchLog()
}
 
func fetchLog() {
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        logItems = fetchResults
    }
}

Maintenant, nous pouvons modifier les méthodes tableView de dataSource, pour nous référer à ce tableau, au lieu de valeurs codées.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
// MARK: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // Combien de lignes sont dans cette section 
    //  Il y a seulement une section et il y a un certain nombre de lignes
    //  égal au nombre de LogItems, donc retourne le count
    return logItems.count
}
 
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("LogCell") as! UITableViewCell
     
    // Obtenir le LogItem pour cet index
    let logItem = logItems[indexPath.row]
     
    //  Définir le titre de la cellule pour être le titre du LogItem
    cell.textLabel?.text = logItem.title
    return cell
}

Cela devrait nous permettre de voir les éléments énumérés dans la vue, mais nous voulons montrer le texte d'un élément quand il est cliqué. Ainsi nous avons besoin de paramétrer la vue afin d'utiliser le contrôleur de vue en tant que délégué de ladite vue ; ainsi nous pourrons recevoir en retour la méthode didSelectRowAtIndexPath.

Comme précédemment, nous allons ajouter le protocole UITableViewDelegate à la classe.

 
Sélectionnez
1.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

Et pour paramétrer le délégué en mode autonome, vous pouvez régler la source de données dans viewDidLoad

 
Sélectionnez
1.
2.
3.
// Ceci indique  à la vue  qu'il devrait obtenir ses données de cette classe, ViewController
logTableView.dataSource = self
logTableView.delegate = self

Finalement, nous pouvons créer la méthode, en sachant que la vue appelle cette méthode lorsqu'une cellule est cliquée.

 
Sélectionnez
1.
2.
3.
4.
5.
// MARK: UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let logItem = logItems[indexPath.row]
    println(logItem.itemText)
}

Ainsi lorsque le bouton est cliqué, un message affiche le itemText dans la console (fenêtre Xcode), pour l'élément sélectionné.

Le but de ce tutoriel n'est pas réellement d'expliquer comment régler une vue manuellement, mais c'est en quelque sorte nécessaire afin d'obtenir un bon aperçu des données. Pour cette raison, je fournis le code source depuis le début de cette partie 2 jusqu'ici.

Image non disponible

L' organisation des résultats peut être différente chaque fois que vous exécutez l'application, car cette dernière n'effectue aucun tri lorsqu'elle exécute une requête. Dans certains entrepôts de données, vous pourriez recevoir en retour ces données dans le même ordre que celui dans lequel elles ont été insérées ; mais avec Core Data, cela finit par être joliment aléatoire. Nous allons corriger cela en ajoutant un tri à la requête fetch.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
func fetchLog() {
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
     
    // Créer un objet descripteur qui trie selon le « title »
    // propriété de l'objet Core Data
    let sortDescriptor = NSSortDescriptor(key: "title", ascending: true)
     
    // Définir la liste des descripteurs de tri dans la requête fetch,
    // donc il contient le descripteur de tri
    fetchRequest.sortDescriptors = [sortDescriptor]
     
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        logItems = fetchResults
    }
}

Maintenant, la requête fetch a son sortDescriptor bien paramétré. Notez qu'il s'agit d'un tableau, c'est pourquoi nous avons besoin de parenthèses seulement autour de sortDescriptor que nous avons créé en utilisant title comme clé. En lançant l'application, vous devriez maintenant voir la liste des éléments triée (alphabétiquement), beaucoup mieux ! Notez que vos données sont escomptées à être différentes.

Image non disponible

Pensons aussi à filtrer certains éléments. Premièrement, essayons seulement d'obtenir les éléments nommés « Best Language ». Nous allons créer un NSPredicate qui utilise une chaîne de caractères pour représenter les prérequis que tout objet doit vérifier afin de passer dans la requête.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
func fetchLog() {
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
     
    // Créer un objet descripteur qui trie selon le « title »
    // propriété de l'objet Core Data
    let sortDescriptor = NSSortDescriptor(key: "title", ascending: true)
     
    // Définir la liste des descripteurs de tri dans la requête fetch,
    // donc il contient le descripteur de tri
    fetchRequest.sortDescriptors = [sortDescriptor]
     
    // Créer un nouveau prédicat qui filtre tout objet qui n'a pas exactement le titre de "Best Language"
    // doesn't have a title of "Best Language" exactly.
    let predicate = NSPredicate(format: "title == %@", "Best Language")
     
    //Définir le prédicat dans la requête fetch
    fetchRequest.predicate = predicate
     
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        logItems = fetchResults
    }
}

Si vous n'avez pas encore vu le format de la syntaxe, ou que vous ne l'avez plus vu depuis un moment, c'est assez simple de dire que chaque fois que vous voyez un format en tant qu'un paramètre nommé, cela provient des méthodes en objective-C (langage C avec objets) « predicateWithFormat », ou « stringWithFormat », et ainsi de suite.

Cela remplace toute instance de symbole %@ par une description d'objet (la valeur d'une chaîne de caractères, ou une représentation autrement utile d'autres types d'objets). Pour les types primitifs tels que Int, vous opterez à la place pour %i, pour les Float, vous opterez pour %f, et ainsi de suite.

Donc lorsque vous voyez

 
Sélectionnez
1.
(format: "title == %@", "Best Language")

Ce que le compilateur prend en compte est semblable à :

 
Sélectionnez
1.
("title == 'Best Language'")

Donc nous spécifions que nous voulons que le title soit égal à cette chaîne de caractères exacte.

Nous pourrions également faire une comparaison de chaînes de caractères en utilisant les mots-clés relatifs aux contenus ; si nous regardons la sous-chaîne de caractères «Worst», nos recevrons seulement les articles qui contiennent cette chaîne…

 
Sélectionnez
1.
2.
3.
4.
5.
// Rechercher uniquement les items utilisant la sous-chaîne « Worst »
let predicate = NSPredicate(format: "title contains %@", "Worst")

// Définir le prédicat dans la requête fetch
fetchRequest.predicate = predicate

Qu'en serait-il si nous combinions cependant les deux ? Nous voulons à la fois les éléments contenant la chaîne de caractères « Worst » et le titre « Best Language » ?

Premièrement, créons les deux prédicats séparément :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
// Créer un nouveau prédicat qui filtre tout objet qui n'a pas exactement le titre de "Best Language"

let firstPredicate = NSPredicate(format: "title == %@", "Best Language")

// Rechercher uniquement les items utilisant la sous-chaîne « Worst »
let thPredicate = NSPredicate(format: "title contains %@", "Worst")

Ensuite, combinons-les en utilisant le constructeur NSCompoundPredicate :

 
Sélectionnez
1.
2.
3.
4.
5.
// Combiner les deux prédicats ci-dessus en un seul prédicat composé
let predicate = NSCompoundPredicate(type: NSCompoundPredicateType.OrPredicateType, subpredicates: [firstPredicate, thPredicate])

// Définir le prédicat dans la requête fetch
fetchRequest.predicate = predicate

Du moment que nous voulons à la fois les cas de « Best Language » et n'importe quel élément contenant « Worst », nous utilisons un prédicat composé de type NSCompoundPredicateType.OrPredicateType.

Tout cela n'est qu'une manière confuse de dire « donne-moi n'importe lequel des éléments où le firstPredicate (premier prédicat) ou le thPredicate est vrai ».

Ce que nous avons fait là est assez puissant en pratique. Nous pouvons utiliser une comparaison de chaînes de caractères comme prédicats, et filtrer ou trier de grandes listes de données à travers le Core Data.

L'API Core Data va alors se connecter au magasin de soutien (SQLite), et produire une requête efficiente pour rapidement regrouper l'information nécessaire. C'est un schéma très commun en développement iOS, et le comprendre est essentiel. Alors, si vous avez été coincé sur ce didacticiel ou que vous avez une quelconque question, n'hésitez pas à demander de l'aide sur les forums.

Cela conclut la partie 2 pour le moment ; dans la partie suivante, nous changerons pour un scénario plus appliqué, en ajoutant pour les utilisateurs une façon de créer des articles de journaux, de les éditer, de les sauver, et de les effacer.

Le code source complet de cette partie.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2016 AUTEUR . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.