Note: document en cours de révision.
Pré-requis:
Vidéo du résultat final:
Dans ce tutoriel, nous verrons comment construire une application qui permet la gestion d’une liste de tâches à réaliser – présentées dans un ‘UITableView’ – , sous forme de mémos éditables.
Les ‘tâches’ seront lues et enregistrées dans un fichier de propriétés, ce qui assurera leur pérennité entre les chargements de l’application.
L’application proposera une liste de tâches à partir de laquelle il sera possible:
Savoir programmer un ‘protocole’ sous O.C.
Explication
Nous allons programmer un protocole, dans la classe de la scène ‘ajouter/modifier’ qui va permettre à la classe de la scène ‘liste des tâches’ de devenir délégué des méthodes ‘ajouterTache’ et ‘modifierTache’ de la classe ‘ajouter-modifier’.
À définir …
ACTION – Ouvrons le projet de départ et testons l’app.
Remarquons que les modifications, ainsi que l »ajout d’une nouvelle tâche, ne sont pas enregistrés.
ACTION – Analysons le schéma de départ:
La délégation du UITableView propose la méthode ‘commitEditingStyle’ qui permet d’activer la suppression d’une cellule sur glissement horizontal.
ACTION – Ajoutons la méthode suivante à la classe ‘VCNotes.m’
// ********************************************************************************* // À compléter // ETAPE 1.2 - Ajouter la méthode 'commitEditingStyle' // Actualiser le tableau des tâches // Actualiser le UITableView // Enregistrer le tableau des tâches dans 'listeDesTaches.plist' // --------------------------------------------------------------------------------- // Méthode de délégation pour gérer la suppression de la cellule sur swap // ################################################################################# - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath // ################################################################################# { if (editingStyle == UITableViewCellEditingStyleDelete) { // Effacer, de tableauDesTaches, // l'élément correspondant à la cellule courante [tableauDesTaches removeObjectAtIndex:indexPath.row]; // Enregistrer le tableau dans le fichier.plist // atomically:YES = enregistrer dans une copie avant de remplacer le fichier original. [tableauDesTaches writeToFile:[[NSBundle mainBundle] pathForResource:@"listeDesTaches" ofType:@"plist" ]atomically:YES]; // Actualiser le UITableView [self.tableView reloadData]; } // FIN -> editingStyle == delete } // ### FIN -> commitEditingStyle // ---------------------------------------------------------------------------------
ACTION – Testons le ‘swap’ sur une des cellules du UITableView:
Si nous exécutons l’application, nous allons remarquer que l’heure affichée des tâches ne correspond pas aux données du fichier ‘listeDesTaches.plist’. Celà s’explique par le fait qu’un NSDate est localisé au fuseau horaire ‘UCT’ lors de sa conversion vers un NSString.
L’utilisation de la classe ‘NSDataFormatter’ va permettre d’utiliser le fuseau horaire de l’appareil lors de la conversion. De plus, il sera possible de programmer les éléments de date à afficher. Par exemple, le nom du jour (lundi) + le numéro du mois + HH:MM, …
Voir le site suivant pour les commandes de formatage d’une date.
ACTION – Ajoutons le code suivant à la méthode ‘cellForRowAtIndexPath’ du fichier ‘VCNotes.m’
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { ... // ETAPE 1.3 - Utiliser un NSDateFormatter pour convertir en format local // --------------------------------------------------------------------------------- NSDateFormatter *unFormateurDeDate = [[NSDateFormatter alloc] init]; [unFormateurDeDate setDateFormat:@"yyyy.MM.dd HH:mm"]; NSString *dateMiseEnForme = [unFormateurDeDate stringFromDate:tacheCourante.date]; cell.date.text = dateMiseEnForme; // --------------------------------------------------------------------------------- // cell.date.text = tacheCourante.date.description; // <- Insérer // devant ... } // ### FIN -> cellForRowAtIndexPath
ACTION – Testons l’application et observons maintenant le format des dates.
Etape 1.4 – Déterminer la cellule qui a déclenché le ‘segue’
Explications …
ACTION – Ajoutons le code suivant à la méthode ‘prepareForSegue’ du fichier ‘VCNotes.m’.
// VCNotes.m -(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { VCAjouterTache * vers = [segue destinationViewController]; // ETAPE 1.4 - Déterminer la cellule du sender // --------------------------------------------------------------------------------- // Note sender == UIButton et non pas UITableViewCell // Donc, voici comment obtenir la position du UITableViewCell: UIButton *button = (UIButton *)sender; CGRect buttonFrame = [button convertRect:button.bounds toView:self.tableView]; NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonFrame.origin]; // ---------------------------------------------------------------------------------
Etape 1.5 – Déterminer la destination du ‘segue’ – 3 choix possibles
Explications…
ACTION – Ajoutons le code suivant à la méthode ‘prepareForSegue’ du fichier ‘VCNotes.m’.
// VCNotes.m -(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { VCAjouterTache * vers = [segue destinationViewController]; ... // ********************************************************************************* // À compléter // ETAPE 1.5 - Déterminer la destination du segue. // - Préparer les données en fn de la destination // --------------------------------------------------------------------------------- // Tester si 'segue' est = 'ajouter' if ([segue.identifier isEqual:@"ajouter"]){ // ETAPE 3.4b - Renseigner le délégué // ... vers.modeAjouter = YES; NSLog(@"Transition vers %@", segue.identifier); } // Tester si 'segue' est = 'modifier' if ([segue.identifier isEqual:@"modifier"]){ // ETAPE 3.5b - Renseigner le délégué // .. vers.modeAjouter = NO; vers.detailTache = [[Tache alloc]initWithTache: tableauDesTaches[indexPath.row]]; // sauvegarder la sélection courante pour le retour de modifierTache indiceTacheCourante = indexPath.row; NSLog(@"Transition vers %@ avec indice: %d", segue.identifier, indexPath.row); } // Tester si 'segue' est = 'detail' if ([segue.identifier isEqual:@"detail"]){ vers.detailTache = [[Tache alloc]initWithTache: tableauDesTaches[indexPath.row]]; NSLog(@"Transition vers %@ avec indice: %d", segue.identifier, indexPath.row); } // --------------------------------------------------------------------------------- } // ### FIN -> prepareForSegue
ACTION – Testons l’application
Télécharger le projet à la fin de l’étape 1
Explications …
Note: Étape 2 est réalisée dans la classe ‘VCAjouterTache’.
ACTION – Ajoutons le code suivant au fichier ‘VCAjouterTache’.
// VCAjouterTache.h // ETAPE 2.1a - Programmer un protocole et la signature de ses méthodes @protocol tacheDelegate <NSObject> @optional -(void) ajouterTache:(Tache*) detailInfo; -(void) modifierTache:(Tache*) detailInfo; @end // --------------------------------------------------------------------------------- @interface VCAjouterTache : UIViewController // ********************************************************************************* // À compléter // ETAPE 2.1b - Définir une propriété pour recevoir l'adresse du délégué // --------------------------------------------------------------------------------- @property id delegate; // ---------------------------------------------------------------------------------
L’appel d’une méthode d’un protocole se fait suite à un test sur [self.delegate respondsToSelector:@selector(uneMéthodeDuProtocole)]. Cela permet de vérifier s’il existe un délégué et si ce dernier a implémenté la méthode en question. Si c’est le cas alors l’appel de la méthode est lancé en utilisant la référence au délégué (self.delegate).
ACTION – Ajoutons le code suivant au fichier ‘VCAjouterTache’.
// VCAjouterTache.m - (IBAction)sauvegarder:(id)sender { NSLog(@"sauvegarder"); ... // ETAPE 2.2 - Si il y a un délégué alors lancer les méthodes de délégation // ajouterTache et modifierTache. // --------------------------------------------------------------------------------- // Si en modeAjouter if (self.modeAjouter) { if ([self.delegate respondsToSelector:@selector(ajouterTache:)]){ NSLog(@"Le délégué répond à ajouterTache..."); [self.delegate ajouterTache:info]; } // selector-ajouterTache } // modeAjouter else // Si modeModifier { if ([self.delegate respondsToSelector:@selector(modifierTache:)]){ NSLog(@"Le délégué répond à modifierTache..."); [self.delegate modifierTache:info]; } // selector-modifierTache } // modeModifier // --------------------------------------------------------------------------------- ... } // sauvegarder
Le protocole de la classe ‘VCAjouterTache’ est maintenant en place. Par contre, il n’y a pas de délégué à cette étape ci. Donc, si nous testons le bouton ‘modifier’ de la première cellule suivi du bouton ‘enregistrer’ nous allons remarquer l’absence du message ‘NSLog(@ »Le délégué répond à modifierTache… »);’.
Il nous reste à souscrire au protocole <tacheDelegate> à partir de la classe ‘VCNotes’.
Note: La numérotation passe à 3.x car les étapes suivantes sont réalisées dans la classe ‘VCNotes’.
Pour souscrire à un ou plusieurs protocole dans une classe il faut:
Note: Le point deux(2) sera réalisé à 3.4b.
Note: Il faut procéder ainsi pour les classes qui ne sont pas dans la librairie des objets en mode GUI sous Xcode. Dans ce cas, il n’est pas possible de tracer un lien entre la propriété ‘delegate’ et l’icon de la classe d’une scène du projet.
ACTION– Ajoutons le code suivant au fichier ‘VCNotes.m’
// VCNotes.m // ETAPE 3.3 - Indiquer que la classe courante doit souscrire au protocole de la // classe 'VCAjouterTache' -> '<tacheDelegate>' // --------------------------------------------------------------------------------- @interface VCNotes () <tacheDelegate> // < ** ici ** > @end
À partir de cette étape, il est maintenant possible de programmer les méthodes de délégation ‘ajouterTache’ et ‘modifierTache’ du protocole ‘tacheDelegate’ de la classe ‘VC’.
Note: Étant donné que les méthodes de délégation étaient dans un bloc ‘@optional’ nous ne sommes pas obligé de les programmer.
Commençons par programmer la méthode ‘ajouterTache’
ACTION– Ajoutons le code suivant au fichier ‘VCNotes.m’
// VCNotes.m // ETAPE 3.4 - Programmer la méthode de délégation 'ajouterTache' // --------------------------------------------------------------------------------- -(void) ajouterTache:(Tache*)info{ NSLog(@"délégation - ajouterTache, info = %@", info); // Ajouter au tableauTaches les données reçues. [tableauDesTaches addObject:[info tacheToDictionary]]; NSLog(@"tableauDesTaches ajouterTache = %@", tableauDesTaches); // Actualiser le UITableView [self.tableView reloadData]; // Enregistrer tableauTaches dans le fichier 'listeDesTaches.plist' // atomically:YES = enregistrer dans une copie avant de remplacer le fichier original. [tableauDesTaches writeToFile:[[NSBundle mainBundle] pathForResource:@"listeDesTaches" ofType:@"plist" ]atomically:YES]; } // ### FIN -> ajouterTache
ACTION– Ajoutons le code suivant au fichier ‘VCNotes.m’
// VCNotes.m -(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender // Tester si 'segue' est = 'ajouter' if ([segue.identifier isEqual:@"ajouter"]){ // ********************************************************************************* // À compléter // ETAPE 3.4b - Renseigner le délégué // --------------------------------------------------------------------------------- vers.delegate = self; // --------------------------------------------------------------------------------- ... }
ACTION – Testons l’application
Note: Vous remarquerez, à la fin de la vidéo, que la modification d’une tâche n’entraine pas l’appel de la méthode ‘modifierTache’ car elle n’est pas implémentée dans la classe ‘VCDetails’.
ACTION– Ajoutons le code suivant au fichier ‘VCNotes.m’
// VCNotes.m // ETAPE 3.5a - Programmer la méthode de délégation 'ModifierTache' // --------------------------------------------------------------------------------- -(void) modifierTache:(Tache*)info{ NSLog(@"retour de la méthode 'modifier', info = %@", info); // Remplacer l'élément courant du tableauTaches par les données reçues. tableauDesTaches[indiceTacheCourante] = [info tacheToDictionary]; NSLog(@"tableauDesTaches élément %d modifié = %@", indiceTacheCourante, tableauDesTaches); // Actualiser le UITableView [self.tableView reloadData]; // Enregistrer tableauTaches dans le fichier 'listeDesTaches.plist' [tableauDesTaches writeToFile:[[NSBundle mainBundle] pathForResource:@"listeDesTaches" ofType:@"plist" ]atomically:YES]; } // ### FIN -> modifierTache // ---------------------------------------------------------------------------------
ACTION– Ajoutons le code suivant au fichier ‘VCNotes.m’
// VCNotes.m -(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender // Tester si 'segue' est = 'modifier' if ([segue.identifier isEqual:@"modifier"]){ // ETAPE 3.5b - Renseigner le délégué // --------------------------------------------------------------------------------- vers.delegate = self; // ---------------------------------------------------------------------------------
ACTION – Testons l’application finale!
Voilà ce qui complète le laboratoire Post’TIM!
Télécharger le projet terminé.