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.
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 :
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…
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().
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 :
class
ViewController
:
UIViewController
,
UITableViewDataSource
{
…et d'ajouter les méthodes actuelles de dataSource…
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.
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.
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 :
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().
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.
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.
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…
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.
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.
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.
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.
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.
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
(
format
:
"title == %@"
,
"Best Language"
)
Ce que le compilateur prend en compte est semblable à :
(
"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…
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 :
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 :
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.