Sur l”iPhone, contrairement aux ordinateurs, vous n’avez qu’une seule fenêtre de disponible pour votre application. Ainsi, l’affichage se fera selon un empilement des vues. Étant débutant, il est assez difficile de le visualiser. Faisons-le ensemble !
Dans cette fiche, nous allons très simplement manipuler deux vues, que l’on enlèvera/affichera grâce à un bouton. Ensuite, nous verrons comment implémenter une gestion plus compliquée, en utilisant un RootViewController.
Commençons simplement !
Tout d’abord créez un nouveau projet de type Window-based Application que vous nommerez GestionVues. Ajoutez ensuite un fichier de type UIViewController que vous nommerez RootViewController. Enfin, ajoutez deux fichiers de type UIView (Objective-C class Subclass of UIView), que vous nommerez respectivement Vue1 et Vue2.
Dans un premier temps, nous allons ajouter la vue du RootViewController à la fenêtre, dans l’application delegate.
Dans GestionVuesAppDelegate.h
#import <UIKit/UIKit.h>
@class
RootViewController;
// 1
@interface
GestionVuesAppDelegate :
NSObject
<UIApplicationDelegate> {
RootViewController *rootViewController;
}
@property
(
nonatomic
, retain)
IBOutlet
UIWindow *window;
@property
(
nonatomic
, retain) RootViewController *rootViewController;
// 2
@end
Dans GestionVuesAppDelegate.m
#import "GestionVuesAppDelegate.h"
#import "RootViewController.h"
@implementation
GestionVuesAppDelegate
@synthesize
window=_window;
@synthesize
rootViewController;
- (
BOOL
)application:(UIApplication *)application didFinishLaunchingWithOptions:(
NSDictionary
*)launchOptions
{
RootViewController *viewController = [[RootViewController alloc] init];
self
.rootViewController = viewController;
[viewController release];
[
self
.window addSubview:rootViewController.view];
// 3
[
self
.window makeKeyAndVisible];
return
YES
;
}
- (
void
)dealloc
{
[rootViewController release];
[_window release];
[
super
dealloc];
}
Rien de nouveau dans ce code, si ce n’est une petite différence avec l’objet rootViewController instancié. En effet, ligne 2, on se sert du mécanisme @property et @synthesize qui, rappelez-vous, remplace les getters/setters.
Ligne 1, nous utilisons le mot-clé @class. Cela permet de spécifier au compilateur que tout usage de RootViewController en tant que nom de classe, dans la déclaration de variables à l’intérieur d’une autre classe (ici GestionVuesAppDelegate) est autorisé.
Ligne 3, on ajoute la vue du RootViewController à la pile de vues une fois que tout s’affichera. Il y aura donc la window, et par dessus, la vue du RootViewController.
Ensuite, dans le RootViewController, nous allons ajouter la vue1.
#import <UIKit/UIKit.h>
#import "Vue1.h"
@interface
RootViewController : UIViewController {
Vue1 *vue1;
}
@end
Dans le .m :
- (
void
)loadView
{
[
super
loadView];
// 1
vue1 = [[Vue1 alloc] initWithFrame:
self
.view.bounds];
[
self
.view addSubview:vue1];
vue1.backgroundColor = [UIColor redColor];
}
- (
void
)dealloc
{
[
super
dealloc];
[vue1 release];
}
Vous remarquerez que le [super loadView] n’est pas proposé par défaut. En fait, c’est parce que vous pouvez choisir vous même quel serait la vue (une scroll view, une table view, … ) sans pour autant l’empiler par dessus une vue inutile
Ensuite, ajoutons dans la vue 1 un bouton qui permettra de changer de vue, ainsi qu’un label pour afficher le nom de la vue.
Dans Vue1.h
#import <UIKit/UIKit.h>
#import "GestionVuesAppDelegate.h"
@interface
Vue1 : UIView {
UILabel *labelStatut;
}
@end
Vue1.m
#import "Vue1.h"
#import "RootViewController.h"
@implementation
Vue1
- (
id
)initWithFrame:(CGRect)frame
{
self
= [
super
initWithFrame:frame];
if
(
self
) {
UIButton
*button = [
UIButton
buttonWithType:UIButtonTypeRoundedRect];
[
self
addSubview:button];
labelStatut = [[UILabel alloc] initWithFrame:CGRectMake(30, 30, 250.0, 30.0)];
labelStatut.textColor = [UIColor blackColor];
labelStatut.backgroundColor = [UIColor clearColor];
labelStatut.text = @
"On est sur la vue 1"
;
[
self
addSubview:labelStatut];
}
return
self
;
}
- (
void
) allerALaVue2:(
id
)sender {
GestionVuesAppDelegate *appDelegate = (GestionVuesAppDelegate*)[[UIApplication sharedApplication] delegate];
// 1
[appDelegate.rootViewController changePourLaVue2];
// 2
}
- (
void
)dealloc
{
[labelStatut release];
[
super
dealloc];
}
@end
Nous allons nous servir du rootViewController pour échanger nos vues, vue1 et vue2. Pour accéder à l’objet rootViewController instancié par l’application delegate, on fait tout d’abord une référence vers le delegate de l’application ligne 1. Ensuite, on appelle la méthode changePourLaVue2 du rootViewController ligne 2.
Faisons le point sur notre pile de vues : par-dessus la vue du rootViewController, nous affichons la vue1, qui contiendra un bouton et un label. On aura donc la window, puis par dessus, la vue du rootViewController. Ensuite, vient s’ajouter la vue1, puis un bouton et un label !
Implémentez Vue2.h et Vue2.m de la même manière. Seules ces lignes changeront :
Il ne reste plus qu’à ajouter ou enlever les bonnes vues. Pour commencer, modifier le RootViewController.h :
#import <UIKit/UIKit.h>
#import "Vue1.h"
#import "Vue2.h"
@interface
RootViewController : UIViewController {
Vue1 *vue1;
Vue2 *vue2;
}
- (
void
) changePourLaVue1;
- (
void
) changePourLaVue2;
@end
Puis ajouter dans le .m
- (
void
) changePourLaVue1 {
[vue2 removeFromSuperview];
// 1
[
self
.view addSubview:vue1];
// 2
}
- (
void
) changePourLaVue2 {
if
(!vue2)
vue2 = [[Vue2 alloc] initWithFrame:
self
.view.bounds];
// 3
[vue1 removeFromSuperview];
[
self
.view addSubview:vue2];
}
C’est l’appel à removeFromSuperview (enlever vue2 de la vue sur laquelle elle repose : rootViewController.view) qui va enlever vue2 ligne 1. Ensuite, on ajoute ligne 2 la vue1 à la vue du rootViewController grâce à la méthode addSubview:.
C’est ici qu’il peut y avoir une confusion. En effet, en enlevant la vue2, on enlève également le bouton et le label. Pourquoi ? Tout simplement parce que le bouton et le label sont ajoutés à la vue2. Ainsi, il faut préciser que la notion de pile de vues comprend des vues avec des sous-vues potentielles.
Finalement, le label et le bouton appartiennent à la vue2. En fait, pour chaque vue, on peut retrouver ses sous-vues grâce à NSArray *sousVues = [maVue subviews];. De fait, un removeFromSuperview enlève la vue et toutes ses sous-vues de la pile.
De même, [maVue superview] renvoi à la vue sur laquelle est posée maVue.
Vous pouvez également déplacer une vue dans la pile, avec les méthodes suivantes :
bringSubviewToFront: Met la vue passée en paramètre au-dessus de la pile de vues.
sendSubviewToBack: Met la vue passée en paramètre en dessous de la pile de vues.
insertSubview:aboveSubview: Insère la vue passée en paramètre 1 au-dessus de la vue passée en paramètre 2.
insertSubview:belowSubview: Insère la vue passée en paramètre 1 en dessous de la vue passée en paramètre 2.
exchangeSubviewAtIndex:withSubviewAtIndex: Échange les vues en fonction de leur index.
Enfin, notez que ligne 3, on teste si la vue2 a déjà été allouée ou non. Si ce n’est pas le cas, on le fait. La raison en est très simple : par défaut, on affiche la vue1. Mais rien ne garantit que l’utilisateur va afficher la vue2. Dans ce cas, pourquoi allouer de la mémoire pour un objet qui ne sera pas utilisé ? C’est une règle d’or que vous devrez respecter par la suite : éviter tout chargement mémoire inutile !
Nous avons vu une manière très simple de faire de la gestion de vues. Mais ce n’est pas vraiment terrible pour une architecture plus complexe ! CVous serez probablement amené par la suite à recourir à un rootViewController qui sera un objet de gestion des contrôleurs au-dessus. Pour cela, imaginez ce cas concret : une application avec un menu (MenuViewController), une vue de jeu (JeuViewController) et une vue de paramètres (ParametresViewController). Pour naviguer facilement entre ses vues, une solution consiste à utiliser un RootViewController, qui servira à changer les vues entre elles. Prenons ce cas et réalisons-le.
Utiliser un RootViewController
Vous allez donc construire cette interface. Commencez par créer un projet Window-based Application que vous nommerez UtiliserUnRoot. Ajoutez ensuite quatre view controllers : RootViewController, JeuViewController, MenuViewController et ParametresViewController.
Ajoutez la vue du rootViewController à la window dans l’application delegate, comme au début de ce tutoriel. Essayez maintenant de construire le schéma suivant :
1. Lorsque le rootViewController apparaît, ajoutez la vue du menu.
2. Ajoutez dans la vue du menuviewcontroller deux boutons qui serviront à se rendre sur la vue du jeu ou la vue des paramètres, ainsi qu’un label affichant le nom de la vue courante.
3. Dans les vues des deux contrôleurs de Jeu et Parametres, ajoutez un bouton Retour au menu ainsi qu’un label indiquant le nom de la vue courante.
Notez que ce sera le RootViewController qui possédera les trois contrôleurs Jeu, Menu, et Parametres.
C’est un très bon exercice, qui vous permettra de vérifier vos acquis !
Voici ce que vous avez dû construire (je vous laisse construire ParametresViewController sur le modèle de JeuViewController) :
RootViewController.h
#import <UIKit/UIKit.h>
@class
MenuViewController;
@class
JeuViewController;
@class
ParametresViewController;
@interface
RootViewController : UIViewController {
MenuViewController *menuViewController;
JeuViewController *jeuViewController;
ParametresViewController *parametresViewController;
}
- (
void
) changePourJeu;
- (
void
) changePourParametres;
@end
RootViewController.m
#import "RootViewController.h"
#import "MenuViewController.h"
#import "JeuViewController.h"
#import "ParametresViewController.h"
@implementation
RootViewController
// ....
- (
void
)dealloc
{
[menuViewController release];
[jeuViewController release];
[parametresViewController release];
[
super
dealloc];
}
// .....
- (
void
)loadView
{
[
super
loadView];
menuViewController = [[MenuViewController alloc] init];
[
self
.view addSubview:menuViewController.view];
}
- (
void
) changePourJeu {
}
- (
void
) changePourParametres {
}
MenuViewController.h
#import <UIKit/UIKit.h>
#import "UtiliserUnRootAppDelegate.h"
@interface
MenuViewController : UIViewController {
UILabel *labelStatut;
}
@end
MenuViewController.m
- (
void
) loadView {
[
super
loadView];
UIButton
*boutonJeu = [
UIButton
buttonWithType:UIButtonTypeRoundedRect];
[boutonJeu setFrame:CGRectMake(20, 20, 160, 30)];
[boutonJeu addTarget:
self
action:
@selector
(changePourJeu:) forControlEvents:UIControlEventTouchUpInside];
[boutonJeu setTitle:@
"Aller dans jeu"
forState:UIControlStateNormal];
[
self
.view addSubview:boutonJeu];
UIButton
*boutonParametres = [
UIButton
buttonWithType:UIButtonTypeRoundedRect];
[boutonParametres setFrame:CGRectMake(20, 60, 200, 30)];
[boutonParametres addTarget:
self
action:
@selector
(changePourParametres:) forControlEvents:UIControlEventTouchUpInside];
[boutonParametres setTitle:@
"Aller dans les paramètres"
forState:UIControlStateNormal];
[
self
.view addSubview:boutonParametres];
labelStatut = [[UILabel alloc] initWithFrame:CGRectMake(30, 30, 260, 30)];
[labelStatut setCenter:
self
.view.center];
labelStatut.text = @
"On est sur le menu"
;
labelStatut.textColor = [UIColor blueColor];
[
self
.view addSubview:labelStatut];
}
- (
void
) changePourJeu : (
id
) sender {
UtiliserUnRootAppDelegate *appDelegate = (UtiliserUnRootAppDelegate*)[[UIApplication sharedApplication] delegate];
[appDelegate.rootViewController changePourJeu];
}
- (
void
) changePourParametres : (
id
) sender {
UtiliserUnRootAppDelegate *appDelegate = (UtiliserUnRootAppDelegate*)[[UIApplication sharedApplication] delegate];
[appDelegate.rootViewController changePourParametres];
}
JeuViewController.h
#import <UIKit/UIKit.h>
#import "UtiliserUnRootAppDelegate.h"
@interface
JeuViewController : UIViewController {
UILabel *labelStatut;
}
@end
JeuViewController.m
- (
void
)loadView
{
[
super
loadView];
UIButton
*boutonMenu = [
UIButton
buttonWithType:UIButtonTypeRoundedRect];
[boutonMenu setFrame:CGRectMake(20, 20, 160, 30)];
[boutonMenu addTarget:
self
action:
@selector
(changePourMenu:) forControlEvents:UIControlEventTouchUpInside];
[boutonMenu setTitle:@
"Retour au menu"
forState:UIControlStateNormal];
[
self
.view addSubview:boutonMenu];
labelStatut = [[UILabel alloc] initWithFrame:CGRectMake(30, 30, 260, 30)];
[labelStatut setCenter:
self
.view.center];
labelStatut.text = @
"On est sur le jeu"
;
labelStatut.textColor = [UIColor redColor];
[
self
.view addSubview:labelStatut];
}
- (
void
) changePourMenu : (
id
) sender {
UtiliserUnRootAppDelegate *appDelegate = (UtiliserUnRootAppDelegate*)[[UIApplication sharedApplication] delegate];
// on appel changePourJeu qui va nous permettre de revenir au menu
[appDelegate.rootViewController changePourJeu];
}
Il ne vous aura pas échappé que nous n’avons écrit aucun code enlevant une vue pour en remettre une autre. Faisons-le pour JeuViewController en implémentant la méthode changePourJeu dans RootViewController.m. Dans cette méthode, nous allons regarder quelle vue du menu ou du jeu s’affiche. Ensuite, nous enlèverons la vue affichée pour mettre l’autre à la place.Vous allez voir, c’est un peu subtil…
- (
void
) changePourJeu {
if
(!jeuViewController) {
// rappelez-vous, on n'alloue que si l'on a besoin
jeuViewController = [[JeuViewController alloc] init];
}
// on fait référence sur les vues de menu et jeu
UIView *menuView = menuViewController.view;
UIView *jeuView = jeuViewController.view;
if
([menuView superview] !=
nil
)
// si menuView repose sur une vue, alors on l'enlève // 1
{
[jeuViewController viewWillAppear:
NO
];
// 2
[menuViewController viewWillDisappear:
NO
];
[menuView removeFromSuperview];
[
self
.view addSubview:jeuView];
[jeuViewController viewDidAppear:
NO
];
[menuViewController viewDidDisappear:
NO
];
}
else
// sinon, c'est jeuView que l'on enlève
{
[menuViewController viewWillAppear:
NO
];
[jeuViewController viewWillDisappear:
NO
];
[jeuView removeFromSuperview];
[
self
.view addSubview:menuView];
[menuViewController viewDidAppear:
NO
];
[jeuViewController viewDidDisappear:
NO
];
}
}
Pour savoir quelle vue enlever, et quelle vue remettre, on teste ligne 1 si la superview de menu ne pointe pas vers nil (pointeur nul). En fait, cela revient à tester si la vue du menu est dans la pile de vue.
Ligne 2, on appelle explicitement les méthodes viewWillAppear:(BOOL)animated, viewWillDisappear:(BOOL)animated… On spécifie non pour l’animation car la transition se fera sans animations. Compilez et lancez votre application pour tester.
Faites de même pour afficher la vue de paramètres.
Pour finir, nous allons ajouter une animation de type flipside (retournement de 180°) à l’aide de méthodes de haut niveau de Core Animation. Modifiez la méthode ci-dessus pour obtenir :
- (
void
) changePourJeu {
if
(!jeuViewController) {
// rappelez-vous, on n'alloue que si l'on a besoin
jeuViewController = [[JeuViewController alloc] init];
}
// on fait référence sur les vues de menu et jeu
UIView *menuView = menuViewController.view;
UIView *jeuView = jeuViewController.view;
[UIView beginAnimations:@
"flipAnimation"
context:
NULL
];
// réinitialiser le layer
[UIView setAnimationDuration:0.5];
// durée de l'animation
if
([menuView superview] !=
nil
)
// si menuView repose sur une vue, alors on l'enlève
{
[jeuViewController viewWillAppear:
YES
];
[menuViewController viewWillDisappear:
YES
];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:
self
.view cache:
YES
];
// type de l'animation
[menuView removeFromSuperview];
[
self
.view addSubview:jeuView];
[jeuViewController viewDidAppear:
YES
];
[menuViewController viewDidDisappear:
YES
];
}
else
// sinon, c'est jeuView que l'on enlève
{
[menuViewController viewWillAppear:
YES
];
[jeuViewController viewWillDisappear:
YES
];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:
self
.view cache:
YES
];
[jeuView removeFromSuperview];
[
self
.view addSubview:menuView];
[menuViewController viewDidAppear:
YES
];
[jeuViewController viewDidDisappear:
YES
];
}
[UIView commitAnimations];
// lancer l'animation
}
Voilà, ce tutoriel est maintenant terminé !!
Les sources GestionVues !
Les sources du Root !
À bientôt