Dans ce tutoriel, nous allons implémenter une version minimaliste de l'interface utilisateur de style Facebook / Path. L'objectif sera de comprendre comment utiliser le confinement du contrôleur de vue afin d'implémenter un flux personnalisé dans votre application..
Les contrôleurs de vue constituent un élément essentiel de toute application iOS, quelle que soit sa taille, sa taille, sa simplicité ou sa complexité. Ils fournissent la "logique de liaison" entre le modèle de données de votre application et l'interface utilisateur.
De manière générale, il existe deux types de contrôleurs de vue:
Un contrôleur de conteneur peut avoir son propre composant visible, mais il fonctionne essentiellement comme hôte pour les contrôleurs de vue de contenu. Les contrôleurs de conteneur servent à "trafiquer" les allées et venues des contrôleurs de vue de contenu.
UINavigationController
, UITabBarController
et UIPageViewController
sont des exemples de contrôleurs de vue de conteneur fournis avec le SDK iOS. Examinez les différences entre les trois applications en termes de flux d'applications. Le contrôleur de navigation est idéal pour une application de type exploration en profondeur, dans laquelle la sélection effectuée par l'utilisateur dans un écran affecte les choix présentés dans l'écran suivant. Le contrôleur de barre d’onglet est idéal pour les applications avec des fonctionnalités indépendantes, permettant un basculement pratique en appuyant simplement sur un bouton d’onglet. Enfin, le contrôleur de visualisation de page présente une métaphore de livre, permettant à l'utilisateur de basculer entre les pages de contenu..
Il est important de garder à l'esprit ici qu'un écran contenant du contenu présenté par l'un de ces contrôleurs de vue de conteneur doit lui-même être géré, à la fois en termes de données dont il est dérivé (le modèle) et de la présentation à l'écran. (la vue), qui serait encore une fois le travail d’un contrôleur de vue. Nous parlons maintenant de contrôleurs de vue de contenu. Dans certaines applications, en particulier sur l'iPad, car son écran plus grand permet d'afficher plus de contenu à la fois, il peut même être nécessaire de gérer différentes vues à l'écran. Cela nécessite plusieurs contrôleurs de vue à l'écran à la fois. Tout cela implique que les contrôleurs de vue dans une application bien conçue doivent être implémentés de manière hiérarchique, les contrôleurs de vue de conteneur et de contenu jouant leurs rôles respectifs..
Avant iOS 5, il n'existait aucun moyen de déclarer une relation hiérarchique (c'est-à-dire parent-enfant) entre deux contrôleurs de vue et, par conséquent, aucun moyen "approprié" d'implémenter un flux d'application personnalisé. Il fallait soit se débrouiller avec les types intégrés, soit le faire de manière aléatoire, consistant essentiellement à coller des vues gérées par un contrôleur de vue dans la hiérarchie de vues de la vue gérée par un autre contrôleur de vue. Cela créerait des incohérences. Par exemple, une vue finirait par être dans la hiérarchie de vues de deux contrôleurs sans que l'un ou l'autre de ces contrôleurs ne reconnaisse l'autre, ce qui conduit parfois à un comportement étrange. Le confinement a été introduit dans iOS 5 et légèrement amélioré dans iOS 6. Il permet de formaliser la notion de contrôleurs de vue parent et enfant dans une hiérarchie.. Essentiellement, le confinement correct du contrôleur de vue exige que si la vue B soit une sous-vue (enfant) de la vue A et si elles ne sont pas gérées par le même contrôleur de vue, le contrôleur de vue de B doit alors être défini sur l'enfant du contrôleur de vue de B.
Vous pourriez vous demander si le confinement du contrôleur de vue offre un avantage concret en plus de l'avantage de la conception hiérarchique dont nous avons parlé. La réponse est oui. N'oubliez pas que lorsqu'un contrôleur de vue s'affiche à l'écran ou s'en va, il peut être nécessaire de configurer ou de supprimer des ressources, de nettoyer, d'extraire ou de sauvegarder des informations depuis / vers le système de fichiers. Nous connaissons tous les rappels d’apparence. En déclarant explicitement la relation parent-enfant, nous nous assurons que le contrôleur parent transmettra les rappels à ses enfants à chaque activation ou désactivation de l'écran. Les rappels de rotation doivent également être transférés. Lorsque l'orientation change, tous les contrôleurs de vue à l'écran doivent être informés pour pouvoir ajuster leur contenu de manière appropriée..
Qu'est-ce que tout cela implique, en termes de code? Les contrôleurs de vue ont un NSArray
propriété appelée childViewControllers
et nos responsabilités incluent l'ajout et la suppression de contrôleurs de vue enfant à et de ce tableau dans le parent en appelant les méthodes appropriées. Ces méthodes incluent addChildViewController
(appelé le parent) et removeFromParentViewController
(appelé à l'enfant) lorsque nous cherchons à faire ou défaire la relation parent-enfant. Deux messages de notification sont également envoyés au contrôleur de vue enfant au début et à la fin du processus d'ajout / suppression. Ceux-ci sont willMoveToParentViewController:
et didMoveToParentViewController:
, envoyé avec le contrôleur parent approprié comme argument. L'argument est néant
, si l'enfant est enlevé. Comme nous le verrons, l’un de ces messages nous sera envoyé automatiquement tandis que l’autre sera sous notre responsabilité. Cela dépend si nous ajoutons ou supprimons l'enfant. Nous étudierons la séquence exacte sous peu lorsque nous implémenterons des choses dans le code. Le contrôleur enfant peut répondre à ces notifications en implémentant les méthodes correspondantes s'il doit faire quelque chose en préparation de ces événements..
Nous devons également ajouter / supprimer les vues associées au contrôleur de vue enfant dans la hiérarchie du parent, en utilisant des méthodes telles que addSubview:
ou removeFromSuperview
), y compris l'exécution d'éventuelles animations d'accompagnement. Il y a une méthode pratique (-) transitionFromViewController: toViewController: durée: options: animations: complétion:
cela nous permet de rationaliser le processus de permutation des contrôleurs de vue enfant à l'écran avec des animations. Nous examinerons les détails exacts lorsque nous écrirons le code - qui est le suivant!
Créer une nouvelle application iOS dans Xcode basée sur le "Application vide"modèle. Faites-en une application iOS avec ARC activé. Appelez-le VCContainmentTut.
Créer un nouveau projetCréez une nouvelle classe appelée RootController
. En faire un UIViewController
sous-classe. Assurez-vous que toutes les cases à cocher sont désélectionnées. Ce sera notre sous-classe de contrôleur de vue de conteneur.
Remplacer le code dans RootViewController.h avec ce qui suit.
#importation@interface RootController: UIViewController // (1) - (id) initWithViewControllers: (NSArray *) viewControllers andMenuTitles: (NSArray *) titres; // (2) @end
Notre contrôleur de conteneur aura une vue sous forme de tableau qui fonctionnera comme notre menu. En tapant sur n'importe quelle cellule, le contrôleur de vue actuellement visible sera remplacé par celui sélectionné par le robinet de l'utilisateur..
Se référant aux points dans le code,
Jetons un coup d'oeil en avant pour voir à quoi notre produit fini ressemblera afin que vous ayez une image mentale à associer à la mise en œuvre..
Cela nous aidera à comprendre que notre contrôleur de vue de conteneur est assez similaire à un contrôleur de vue à onglet. Chaque élément du menu correspond à un contrôleur de vue indépendant. La différence entre nos "menu coulissant"contrôleur et le contrôleur de tabulation sont visuels pour la plupart. La phrase"menu coulissant"est un peu impropre, car c’est en fait le contrôleur de vue du contenu qui glisse pour masquer ou afficher le menu situé en dessous.
Passons maintenant à l'implémentation, remplacez tout le code dans RootController.m par le code suivant.
#define kExposedWidth 200.0 #define kMenuCellID @ "MenuCell" #import "RootController.h" @interface RootController () @property (nonatomic, strong) UITableView * menu; @property (nonatomic, strong) NSArray * viewControllers; @property (nonatomic, strong) NSArray * menuTitles; @property (nonatomic, assign) NSInteger indexOfVisibleController; @property (nonatomic, assign) BOOL isMenuVisible; @end @implementation RootController - (id) initWithViewControllers: (NSArray *) viewControllers andMenuTitles: (NSArray *) menuTitles if (self = [super init]) NSAssert (self.viewControllers.count == self.menuTitles.count, @ "Il doit y avoir un et un seul titre de menu correspondant à chaque contrôleur de vue!"); // (1) NSMutableArray * tempVCs = [NSMutableArray arrayWithCapacity: viewControllers.count]; self.menuTitles = [copie de menuTitles]; for (UIViewController * vc dans viewControllers) // (2) if (! [vc isMemberOfClass: [classe UINavigationController]]) tempVCs addObject: [[UINavigationController alloc]] initWithRootViewController: vc]]; else [tempVCs addObject: vc]; UIBarButtonItem * discoverMenuBarButtonItem = [[UIBarButtonItem alloc]] initWithTitle: @ style "Menu": UIBarButtonItemStylePlain cible: auto-action: @selector (toggleMenuVisibility :)); // (3) UIViewController * topVC = ((UINavigationController *) tempVCs.lastObject) .topViewController; topVC.navigationItem.leftBarButtonItems = [@ [veMenuBarButtonItem] arrayByAddingObjectsFromArray: topVC.navigationItem.leftBarButtonItems]; self.viewControllers = [copie de tempVC]; self.menu = [[UITableView alloc] init]; // (4) self.menu.delegate = self; self.menu.dataSource = self; retourner soi-même; - (void) viewDidLoad [super viewDidLoad]; [self.menu registerClass: [Classe UITableViewCell] pourCellReuseIdentifier: kMenuCellID]; self.menu.frame = self.view.bounds; [self.view addSubview: self.menu]; self.indexOfVisibleController = 0; UIViewController * visibleViewController = self.viewControllers [0]; visibleViewController.view.frame = [auto offScreenFrame]; [self addChildViewController: visibleViewController]; // (5) [self.view addSubview: visibleViewController.view]; // (6) self.isMenuVisible = YES; [self adjustContentFrameAccordingToMenuVisibility]; // (7) [self.viewControllers [0] didMoveToParentViewController: self]; // (8) - (void) toggleMenuVisibility: (id) expéditeur // (9) self.isMenuVisible =! Self.isMenuVisible; [self adjustContentFrameAccordingToMenuVisibility]; - (void) adjustContentFrameAccordingToMenuVisibility // (10) UIViewController * visibleViewController = self.viewControllers [self.indexOfVisibleController]; CGSize size = visibleViewController.view.frame.size; if (self.isMenuVisible) [UIView animateWithDuration: 0.5 animations: ^ visibleViewController.view.frame = CGRectMake (kExposedWidth, 0, size.width, size.height); ]; else [UIView animateWithDuration: 0.5 animations: ^ visibleViewController.view.frame = CGRectMake (0, 0, size.width, size.height); ]; - (void) replaceVisibleViewControllerWithViewControllerAtIndex: (NSInteger) index // (11) if (index == self.indexOfVisibleController) return; UIViewController * incomingViewController = self.viewControllers [index]; incomingViewController.view.frame = [auto offScreenFrame]; UIViewController * outgoingViewController = self.viewControllers [self.indexOfVisibleController]; CGRect visibleFrame = self.view.bounds; [outgoingViewController willMoveToParentViewController: nil]; // (12) [self addChildViewController: incomingViewController]; // (13) [[UIApplication sharedApplication] beginIgnoringInteractionEvents]; // (14) [auto transitionFromViewController: outgoingViewController // (15) toViewController: incomingViewController durée: 0.5 options: 0 animations: ^ outgoingViewController.view.frame = [auto offScreenFrame]; complétion: ^ (BOOL terminé) [UIView animateWithDuration: 0.5 animations: ^ [outgoingViewController.view removeFromSuperview]; [self.view addSubview: incomingViewController.view]; incomingViewController.view.frame = visibleFrame; [[UIApplication sharedApplication] endIgnoringInteractionEvents]; // (16)]; [incomingViewController didMoveToParentViewController: self]; // (17) [outgoingViewController removeFromParentViewController]; // (18) self.isMenuVisible = NO; self.indexOfVisibleController = index; ]; // (19): - (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView return 1; - (NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) section return self.menuTitles.count; - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: kMenuCellID]; cell.textLabel.text = self.menuTitles [indexPath.row]; cellule de retour; - (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath [self replaceVisibleViewControllerWithViewControllerAtIndex: indexPath.row]; - (CGRect) offScreenFrame return CGRectMake (self.view.bounds.size.width, 0, self.view.bounds.size.width, self.view.bounds.size.height); @fin
Maintenant, pour une explication du code. Les parties sur lesquelles j'ai mis l'accent sont particulièrement pertinentes pour la mise en œuvre du confinement.
UIViewController
et NSString
types respectivement. Vous pourriez envisager de le faire. Notez que nous maintenons des tableaux pour chacun d’eux, appelés viewControllers
, et menuTitres
.viewDidLoad
, après avoir configuré et ajouté la vue de la table des menus à la vue de notre contrôleur racine, nous introduisons dans notre application le premier contrôleur de vue du viewControllers
tableau. En envoyant le addChildViewController:
message à notre contrôleur racine, nous assumons notre première responsabilité liée au confinement. Vous devriez savoir que cela provoque le message willMoveToParentViewController:
être appelé sur le contrôleur enfant.soi
, l'instance RootController, en tant qu'argument. Dans notre contrôleur enfant, nous pouvons implémenter cette méthode si nous devons.adjustContentFrameAccordingToMenuVisibility
nous permet d'ajuster le cadre du contrôleur d'affichage du contenu pour nous indiquer si le menu est masqué ou non. Si oui, alors cela recouvre le superview. Sinon, il est déplacé vers la droite par kExposedWidth
. J'ai mis ça à 200 points.replaceVisibleViewControllerWithViewControllerAtIndex
nous permet d’échanger les contrôleurs de vue et les vues correspondantes de la hiérarchie. Pour terminer notre animation, qui consiste à faire glisser le contrôleur de vue remplacé hors écran vers la droite, puis à importer le contrôleur de remplacement à partir du même endroit, nous définissons des cadres rectangulaires..néant
. Une fois cette étape terminée, ce contrôleur de vue cessera de recevoir les rappels d’apparence et de rotation du parent. Cela a du sens car ce n'est plus une partie active de l'application.didMoveToParentViewController
message avec soi comme argument.removeFromParentViewController
message. Tu devrais savoir ça didMoveToParentViewController:
avec néant
comme argument est envoyé pour vous.-tableView: didSelectRowAtIndexPath:
méthode.Vous avez peut-être trouvé la séquence d'appels liés à la maîtrise du contrôleur un peu déroutante. Ça aide à résumer.
addChildViewController:
sur le parent avec l'enfant comme argument. Cela provoque le message willMoveToParentViewController:
à envoyer à l'enfant avec le parent comme argument.didMoveToParentViewController:
sur l'enfant avec le parent comme argument.willMoveToParentViewController:
sur l'enfant avec néant
comme argument.removeFromParentViewController
à l'enfant. Les causes du message didMoveToParentViewController
avec néant
comme argument à envoyer à l'enfant en votre nom.Testons les différents types de contrôleurs de vue ajoutés à notre contrôleur racine! Créer une nouvelle sous-classe de UIViewController
appelé ViewController
, garder toutes les options décochées.
Remplacez le code dans ViewController.m par le code suivant.
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void) willMoveToParentViewController: (UIViewController *) parent NSLog (@ "% @ (% p) -% @", NSStringFromClass ((classe d'auto) ), self, NSStringFromSelector (_cmd)); - (void) didMoveToParentViewController: (UIViewController *) parent NSLog (@ "% @ (% p) -% @", NSStringFromClass ([self class]), self, NSStringFromSelector (_cmd)); - (void) viewWillAppear: (BOOL) animated [super viewWillAppear: animated]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([self class]), self, NSStringFromSelector (_cmd)); - (void) viewDidAppear: (BOOL) animated [super viewDidAppear: animated]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([self class]), self, NSStringFromSelector (_cmd)); - (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation durée: (NSTimeInterval) duration [super willRotateToInterfaceOrientation: toInterfacefaceOrientation duration: duration]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([self class]), self, NSStringFromSelector (_cmd)); - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation [super didRotateFromInterfaceOrientation: fromInterfaceOrientation]; NSLog (@ "% @ (% p) -% @", NSStringFromClass ([self class]), self, NSStringFromSelector (_cmd)); @fin
Notre contrôleur de vue lui-même n’a rien de spécial, si ce n’est que nous avons ignoré les divers rappels afin que nous puissions les consigner chaque fois que notre ViewController l'instance devient un enfant pour notre contrôleur racine et une apparence ou un événement de rotation se produit.
Dans tout le code précédent, _cmd
fait référence au sélecteur correspondant à la méthode selon laquelle notre exécution est à l'intérieur. NSStringFromSelector ()
le convertit en chaîne. C'est un moyen rapide et facile d'obtenir le nom de la méthode actuelle sans avoir à le taper manuellement.
Lançons un contrôleur de navigation et un contrôleur de tabulation dans le mix. Cette fois, nous allons utiliser les Storyboards.
Créer un nouveau fichier, et sous iOS> Interface utilisateur, choisir story-board. Définissez la famille d'appareils sur iPhone, et nommez-le NavStoryBoard.
Du bibliothèque d'objets, glisser et déposer un Contrôleur de navigation objet dans la toile. Glissez et déposez un élément de bouton de barre dans le côté gauche de la barre de navigation dans le contrôleur de vue de table désigné "Contrôleur de vue racine". Ceci contient la vue tabulaire de la toile. Donnez-lui un nom. Je l'ai nommée"La gauche"Son but est de vérifier le code que nous avons écrit pour que le bouton Cacher / Révéler de la barre de menus prenne sa place comme le bouton le plus à gauche de la barre de navigation, en poussant tous les boutons déjà présents vers la droite. Contrôleur de vue exemple et placez-le à la droite du contrôleur intitulé "Contrôleur de vue racine"dans la toile.
Cliquez là où il est écrit "Vue tableau"au centre du second contrôleur, et dans le inspecteur d'attributs changer le contenu de "Prototype dynamique" à "Cellules statiques".
Modification du type de contenu de cellule de dynamique à statiqueCela entraînera l'apparition de trois cellules de vue de table statique dans le générateur d'interface. Supprimer toutes les cellules de la vue tableau sauf une, tout en maintenant enfoncée Contrôle, cliquez et faites glisser de la cellule restante vers le contrôleur de vue à l'extrême droite, puis relâchez. Sélectionnez "pousser" sous Sélection Segue. Tout cela ne fait que provoquer une transition vers le contrôleur de vue de droite lorsque vous appuyez sur la cellule isolée de la vue Tableau. Si vous voulez, vous pouvez déposer un UILabel sur la cellule du tableau pour lui donner un texte. Votre story-board devrait ressembler à la photo ci-dessous.
NavStoryBoardEnfin, ajoutons un contrôleur de barre d’onglet. Comme vous l’avez fait précédemment, créez un story-board déposer et appeler TabStoryBoard. Glissez et déposez un contrôleur de barre d'onglets article de la bibliothèque d'objets dans la toile. Il est préconfiguré avec deux onglets et, si vous le souhaitez, vous pouvez modifier la couleur d'arrière-plan des deux contrôleurs de vue à onglets en cliquant sur la vue correspondant à l'un ou l'autre des contrôleurs de vue et en modifiant le "Contexte"option dans le Inspecteur d'attributs. De cette façon, vous pouvez vérifier que la sélection du contrôleur de vue via l'onglet fonctionne correctement.
Votre story-board devrait ressembler à ceci.Maintenant il est temps de tout mettre en place dans le AppDéléguer.
Remplacez le code dans AppDelegate.m par le code suivant.
#import "AppDelegate.h" #import "RootController.h" #import "ViewController.h" @implementation AppDelegate - (BOOL) application: (UIApplication *) application didFinishLaunchingWithOptions: (NSDictionary *) launchOptions self.wind = alloc] initWithFrame: [[UIScreen mainScreen]]]; UIStoryboard * tabStoryBoard = [Ensemble de UIStoryboard storyboardWithName: @ "TabStoryboard": nil]; UIStoryboard * navStoryBoard = [Lot de UIStoryboard storyboardWithName: @ "NavStoryboard": nil]; UINavigationController * navController = [navStoryBoard instantiateViewControllerWithIdentifier: @ "Contrôleur de navigation"]; UITabBarController * tabController = [tabStoryBoard instantiateViewControllerWithIdentifier: @ "Contrôleur d'onglets"]; ViewController * redVC, * greenVC; redVC = [[ViewController alloc] init]; greenVC = [[ViewController alloc] init]; redVC.view.backgroundColor = [UIColor redColor]; greenVC.view.backgroundColor = [UIColor greenColor]; RootController * menuController = [[RootController alloc] initWithViewControllers: @ [tabController, redVC, greenVC, navController] andMenuTitles: @ [@ "Tab", @ "Rouge", @ "Vert", @ "Vert", @ "Nav"]]; self.window.rootViewController = menuController; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; retourner OUI;
Nous avons simplement créé des instances de ViewController
et instancier le contrôleur de navigation et de tabulation à partir des deux story-boards. Nous les avons passés dans un tableau à notre RootController
par exemple. C'est le contrôleur de conteneur que nous avons mis en place au début. Nous l'avons fait avec un tableau de chaînes pour nommer les contrôleurs de vue dans le menu. Maintenant, nous allons simplement désigner notre instance de contrôleur racine initialisée comme étant celle de la fenêtre. rootViewController
propriété.
Construisez et lancez l'application. Vous venez de mettre en œuvre le confinement des conteneurs! Appuyez sur les différentes cellules du tableau dans le menu pour remplacer la diapositive visible par la nouvelle glissière à partir de la droite. Remarquez comment, pour l'instance du contrôleur de navigation (nommée "NavC"dans le menu), le"La gauche"Le bouton a été déplacé d’un endroit vers la droite et le bouton de la barre de menu a été placé dans la position la plus à gauche. Vous pouvez modifier l’orientation en paysage et vérifier que tout semble correct..
Captures d'écran du simulateurDans ce didacticiel d'introduction, nous avons examiné la mise en œuvre d'iOS 6 dans le confinement du contrôleur de vue. Nous avons développé une version simple d'une interface d'application personnalisée qui a gagné en popularité et qui est souvent utilisée dans les applications très utilisées telles que Facebook et Path. Notre implémentation était aussi simple que possible, nous avons donc été en mesure de la disséquer facilement et de bien comprendre les bases. Il existe de nombreuses implémentations open-source sophistiquées de ce type de contrôleur que vous pouvez télécharger et étudier. Une recherche rapide sur Google JASidePAnels
et SWRevealViewController
, parmi d'autres.
Voici quelques idées sur lesquelles vous pouvez travailler.
Une chose que je voudrais mentionner ici est qu’à partir de Xcode 4.5, il