Il y a quelques jours une personne me demandait comment récupérer une donnée d’une vue modale s’affichant avec une transition « Partial Curl » (soulèvement partiel de la vue parente comme une page d’un livre) dans son UIViewController
parent.
Son problème étant que son code fonctionne parfaitement sous iOS 4 mais pas sous iOS 5.
Je regarde son code et là je remarque que tout est bien compliqué pour une chose aussi simple.
En effet, il observe la valeur de sa vue modale et si elle est à nil (l’animation de fermeture de la vue a eu lieu) alors il déclenche un callback sensé transmettre une donnée à sa vue parente, représentée par une propriété de type UIViewController
, sachant que la vue modale se ferme elle-même.
Seulement, le changement d’état de la vue modale n’est pas intercepté et donc la valeur n’est pas transmise.
Outre ce soucis, il aurait dû paraître étrange que la vue modale se supprime et qu’elle possède une propriété UIViewController
car dans iOS (et bien d’autres langages objets) on prend la bonne habitude d’étendre les classes de base et ce pour permettre la réutilisabilité par exemple.
Dans iOS, un design pattern est très souvent utilisé, c’est le delegate (délégation ou proxy).
Le principe est simple : déléguer à un autre objet des traitements, c’est une inversion de responsabilité.
Pour démontrer que c’est simple, je vais vous indiquer les étapes à suivre sous iOS 5 avec les storyboards :
– créez un nouveau projet de type « Single View Application » dans iOS
– déposez un UIButton
dans la vue du contrôleur principal de votre storyboard
– donnez lui un titre
– déposez un nouveau UIViewController
dans votre storyboard
– faîtes un segue modal (ça se prononce ségoué) à partir du bouton vers le nouveau UIViewController
(pour rappel : le segue va instancier le nouveau UIViewController
et faire la transition comme définie dans le code ou l’inspecteur)
– cliquez sur le segue et donnez lui un identifiant
– cliquez sur le nouveau UIViewController
et fixez le style de transition à « Partial Curl » ou bien faîtesle directement sur le segue.
– ajoutez un UIButton
par exemple
– créez une nouvelle classe héritant de UIViewController
: PageCurlController
et ajouter un protocole PageCurlDelegate
déclarant une méthode (style dismissWithData:
), dans votre déclaration de classe ainsi qu’une propriété de type id
répondant à ce protocole afin d’être réutilisable. Vous pouvez l’appeler… delegate 😉
Ce qui est important : la référence vers cet objet est faible (« weak ») car le contraire n’aurait aucun sens ici, si la référence était forte alors si PageCurlController
disparaît le délégué aussi mais comme c’est son parent… tout disparaît ! On ne veut pas cela n’est-ce pas ?
@protocol PageCurlDelegate
-(void)dismissWithData:(NSString *)data;
@end
@interface PageCurlController : UIViewController
@property(nonatomic, weak) id delegate;
@end
– n’oubliez pas de la synthétiser :
@synthesize delegate = _delegate;
– retournez dans le storyboard et changer la classe du nouveau UIViewController
en PageCurlController
(par exemple) dans l’inspecteur d’identité
– reliez le bouton à l’action closePage
, c’est ici que l’on fait appel au délégué afin de lui dire que PageCurlController
est prêt à disparaître et qu’il lui transmet une donnée.
- (IBAction)closePage:(UIButton *)sender
{
//faisons appel au delegate afin de transmettre nos données
//et de fermer cette vue
[_delegate dismissWithData:sender.titleLabel.text];
}
– on complète notre contrôleur principal (logiquement le pointeur est donc fort sur page
) :
@interface PageTrickViewController : UIViewController
@property(nonatomic, strong) IBOutlet PageCurlController * page;
@end
– et dans l’implémentation (on n’oublie pas de synthétiser) :
@synthesize page = _page;
//méthode déclenchée à chaque fois qu'un segue est déclenché
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
//vérification du segue déclenché
if([segue.identifier isEqualToString:@"modalCurl"]){
//ici l'exemple est simple mais il serait judicieux de tester la classe
//avec isKindOfClass !
_page = segue.destinationViewController;
//très important !
_page.delegate = self;
}
}
-(void)dismissWithData:(NSString *)data
{
//ici j'ai juste affiché dans le debug la String transmise
//mais on peut imaginer la stocker dans une ivar ou l'employer dans une autre méthode
NSLog(@"%@", data);
//c'est notre contrôleur principal qui supprime la vue !
[_page dismissModalViewControllerAnimated:YES];
//si vous n'avez plus besoin de _page
_page = nil;
}
Et voici le résultat en image :
Pour les curieux, voici comment Apple voit la délégation : http://developer.apple.com/library/ios/#documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html