Post'TIM

Mise en place d’un protocole O.C

Note: document en cours de révision.

Contenu

Post'TIM

Pré-requis:

  1. Avoir complété le tutoriel TIMFlix ou bien avoir une maitrise de l’objet UITableView + segue + passage de paramètres entre les scènes d’une app.
  2. Avoir complété le module POO sous OC

 
 


 
Vidéo du résultat final:

 


 

Description du projet

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:

Objectif principal

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’.

Objectifs secondaires

À définir …
 


 

Étape 1

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.
 

Étape 1.1

ACTION – Analysons le schéma de départ:
Post-TIM_-_projet_de_depart-schema

Étape 1.2 – Activer la suppression des UITableViewCell sur ‘swap’.

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:
 
PostTIM-delete
 
 

Étape 1.3 – Utiliser un ‘NSDateFormatter’ pour afficher une date en heure locale…

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.
POSTTIM-date-UCT
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.
POSTTIM-date-locale
 
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
 


 

Étape 2  – Programmer un protocole O.C

Explications …
Note: Étape 2 est réalisée dans la classe ‘VCAjouterTache’.

Étape 2.1a et 2.1b – Programmation du protocole ‘tacheDelegate’

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;
// ---------------------------------------------------------------------------------

 

Étape 2.2 – Programmer l’appel des méthodes d’un protocole

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’.


 

Étape 3.3 – Souscrire au protocole <tacheDelegate>.

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:

  1. Énumérer les protocoles entre <p1,p2, …> au bout de la ligne @interface du fichier.m de la classe.
  2. Placer l’adresse (self) de la classe dans la propriété ‘delegate’ de la classe qui propose le protocole.

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’
 

Étape 3.4a – Programmation de 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

Étape 3.4b – Renseigner la propriété ‘delegate’ pour la destination ‘ajouter’.


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’.
 

Étape 3.5a – Programmation de la méthode ‘ajouterTache’


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
// ---------------------------------------------------------------------------------

 

Étape 3.5b – Renseigner la propriété ‘delegate’ pour la destination ‘modifier’.


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é.