104 votes

Que fait réellement addChildViewController ?

Je me lance pour la première fois dans le développement iOS, et l'une des premières choses que j'ai dû faire est d'implémenter une fonction contrôleur de vue de conteneur personnalisé - appelons-le SideBarViewController - qui change le contrôleur de vue enfant qu'il affiche, parmi plusieurs possibles, presque exactement comme un contrôleur de vue enfant standard. Contrôleur de la barre d'onglets . (C'est à peu près un Contrôleur de la barre d'onglets mais avec un menu latéral masquable au lieu d'une barre d'onglets).

Conformément aux instructions de la documentation d'Apple, j'appelle addChildViewController chaque fois que j'ajoute un ViewController enfant à mon conteneur. Mon code pour remplacer le contrôleur de vue enfant actuel affiché par la balise SideBarViewController ressemble à ça :

- (void)showViewController:(UIViewController *)newViewController {
    UIViewController* oldViewController = [self.childViewControllers 
                                           objectAtIndex:0];

    [oldViewController removeFromParentViewController];
    [oldViewController.view removeFromSuperview];

    newViewController.view.frame = CGRectMake(
        0, 0, self.view.frame.size.width, self.view.frame.size.height
    );
    [self addChildViewController: newViewController];
    [self.view addSubview: newViewController.view];
}

Puis j'ai commencé à essayer de comprendre ce que addChildViewController fait ici, et je me suis rendu compte que je n'en ai aucune idée. En plus de coller le nouveau ViewController dans le .childViewControllers le tableau, il semble n'avoir aucun effet sur quoi que ce soit. Les actions et les sorties de la vue du contrôleur enfant vers le contrôleur enfant que j'ai définies dans le storyboard fonctionnent toujours très bien, même si je n'appelle jamais addChildViewController et je ne peux pas imaginer ce que ça pourrait affecter d'autre.

En effet, si je réécris mon code pour ne pas appeler addChildViewController et ressemblent plutôt à ceci...

- (void)showViewController:(UIViewController *)newViewController {

    // Get the current child from a member variable of `SideBarViewController`
    UIViewController* oldViewController = currentChildViewController;

    [oldViewController.view removeFromSuperview];

    newViewController.view.frame = CGRectMake(
        0, 0, self.view.frame.size.width, self.view.frame.size.height
    );
    [self.view addSubview: newViewController.view];

    currentChildViewController = newViewController;
}

... alors mon application fonctionne toujours parfaitement, pour autant que je puisse dire !

La documentation d'Apple n'apporte pas beaucoup d'éclaircissements sur ce qu'il faut faire. addChildViewController fait, ou pourquoi nous sommes supposés l'appeler. Toute l'étendue de la description pertinente de ce que fait la méthode ou de la raison pour laquelle on doit l'utiliser dans sa section dans les UIViewController Référence de la classe est, à l'heure actuelle :

Ajoute le contrôleur de vue donné comme enfant. ... Cette méthode est uniquement destinée à être appelée par une implémentation d'un contrôleur de vue de conteneur personnalisé. Si vous surchargez cette méthode, vous devez appeler super dans votre implémentation.

Il y a aussi ce paragraphe plus tôt sur la même page :

Votre contrôleur de vue de conteneur doit associer un contrôleur de vue enfant à lui-même avant d'ajouter la vue racine de l'enfant à la hiérarchie des vues. Cela permet à iOS d'acheminer correctement les événements vers les contrôleurs de vue enfant et les vues que ces contrôleurs gèrent. De même, après avoir supprimé la vue racine d'un enfant de sa hiérarchie de vues, il doit déconnecter ce contrôleur de vue enfant de lui-même. Pour établir ou rompre ces associations, votre conteneur appelle des méthodes spécifiques définies par la classe de base. Ces méthodes ne sont pas destinées à être appelées par les clients de votre classe de conteneur ; elles doivent être utilisées uniquement par l'implémentation de votre conteneur pour fournir le comportement de confinement attendu.

Voici les méthodes essentielles que vous pourriez avoir besoin d'appeler :

addChildViewController :
removeFromParentViewController
willMoveToParentViewController :
didMoveToParentViewController :

mais il n'offre aucun indice sur ce que sont les "événements" ou le "comportement de confinement attendu" dont il parle, ni pourquoi (ou même quand) l'appel de ces méthodes est "essentiel".

Les exemples de contrôleurs de vue de conteneur personnalisés dans la section "Custom Container View Controllers" de la documentation Apple font tous appel à cette méthode. Je suppose donc qu'elle a un rôle important à jouer, au-delà du simple fait de placer le ViewController enfant dans un tableau, mais je n'arrive pas à savoir quel est ce rôle. Que fait cette méthode, et pourquoi devrais-je l'appeler ?

3 votes

Apple WWDC 2011 La page des vidéos a une grand ("Implementing UIViewController Containment") sur ce sujet.

111voto

Hejazi Points 5981

Je pense qu'un exemple vaut mille mots.

Je travaillais sur une application de bibliothèque et je voulais montrer une belle vue de bloc-notes qui apparaît lorsque l'utilisateur veut ajouter une note.

enter image description here

Après avoir essayé quelques solutions, j'ai fini par inventer ma propre solution personnalisée pour afficher le bloc-notes. Ainsi, lorsque je veux afficher le bloc-notes, je crée une nouvelle instance de NotepadViewController et ajoutez sa vue Racine en tant que vue secondaire de la vue principale. Jusqu'à présent, tout va bien.

J'ai ensuite remarqué que l'image du bloc-notes est partiellement cachée sous le clavier en mode paysage.

enter image description here

Je voulais donc changer l'image du bloc-notes et la décaler vers le haut. Pour ce faire, j'ai écrit le code approprié dans le fichier willAnimateRotationToInterfaceOrientation:duration: mais lorsque j'ai lancé l'application, rien ne s'est produit ! Et après avoir débogué, j'ai remarqué qu'aucun des éléments de la méthode UIViewController est en fait appelé dans la méthode de rotation de NotepadViewController . Seules les méthodes du contrôleur de vue principal sont appelées.

Pour résoudre ce problème, j'avais besoin d'appeler toutes les méthodes à partir de NotepadViewController manuellement lorsqu'ils sont appelés dans le contrôleur de vue principal. Cela va vite compliquer les choses et créer une dépendance supplémentaire entre des composants non liés dans l'application.

C'était dans le passé, avant l'introduction du concept de contrôleur de vue enfant. Mais maintenant, il suffit de addChildViewController au contrôleur de la vue principale et tout fonctionnera comme prévu sans autre travail manuel.

Edit : Il existe deux catégories d'événements qui sont transmis aux contrôleurs de vue enfant :

1- Méthodes d'apparence :

- viewWillAppear:
- viewDidAppear:
- viewWillDisappear:
- viewDidDisappear:

2- Méthodes de rotation :

- willRotateToInterfaceOrientation:duration:
- willAnimateRotationToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation:

Vous pouvez également contrôler les catégories d'événements que vous souhaitez voir transférées automatiquement en modifiant les paramètres suivants shouldAutomaticallyForwardRotationMethods y shouldAutomaticallyForwardAppearanceMethods .

0 votes

D'après la documentation et après avoir effectué un test rapide, je ne pense pas qu'il existe un autre événement qui ne soit transmis que si vous addChildViewController au contrôleur parent.

0 votes

Souhaite qu'il transmette automatiquement viewWillLayoutSubviews

97voto

nevan king Points 46410

Je me posais aussi cette question. J'ai regardé Session 102 de la WWDC 2011 vidéos et M. View Controller, Bruce D. Nilo a dit ceci :

viewWillAppear: , viewDidAppear: etc. n'ont rien à voir avec addChildViewController: . Tout cela addChildViewController: est de dire "Ce contrôleur de vue est un enfant de celui-là" et cela n'a rien à voir avec l'apparence de la vue. Le moment où ils sont appelés est associé au moment où les vues se déplacent dans et hors de la hiérarchie de la fenêtre.

Il semble donc que l'appel à addChildViewController: fait très peu. Les effets secondaires de l'appel sont la partie importante. Ils proviennent de la parentViewController y childViewControllers relations. Voici quelques-uns des effets secondaires que je connais :

  • Transfert des méthodes d'apparence aux contrôleurs de vue enfants
  • Méthodes de rotation de l'envoi
  • (Éventuellement) transmission des avertissements de mémoire
  • Éviter les hiérarchies de CV incohérentes, surtout dans les cas suivants transitionFromViewController:toViewController:… où les deux sociétés de capital-risque doivent avoir le même parent.
  • Permettre aux contrôleurs de vue de conteneurs personnalisés de prendre part à la préservation et à la restauration de l'État
  • Participer à la chaîne des intervenants
  • Accrocher le navigationController , tabBarController propriétés, etc.

0 votes

C'est la session 102 et non 101

2 votes

+1 pour la chaîne de réponse. addChildViewController est nécessaire si vous souhaitez recevoir des événements tactiles sur une vue secondaire appartenant à un UIViewController enfant.

10voto

-[UIViewController addChildViewController:] ajoute seulement le contrôleur de vue transmis dans un tableau de viewControllers dont un viewController (le parent) veut garder la référence. Vous devez en fait ajouter vous-même les vues de ces viewController à l'écran en les ajoutant comme sous-vues d'une autre vue (par exemple, la vue du viewController parent). Il existe également un objet pratique dans Interface Builder pour utiliser les childViewControllers dans les Storyboards.

Auparavant, pour conserver la référence des autres viewControllers dont vous utilisiez les vues, vous deviez conserver manuellement leur référence dans @properties. Le fait d'avoir une propriété intégrée comme childViewControllers et par conséquent parentViewController est un moyen pratique de gérer ces interactions et de construire des viewControllers composés comme le UISplitViewController que l'on trouve sur les applications iPad.

En outre, les childViewControllers reçoivent automatiquement tous les événements système que reçoit le parent : -viewWillAppear, -viewWillDisappear, etc. Auparavant, vous auriez dû appeler ces méthodes manuellement sur vos "childrenViewControllers".

C'est tout.

0 votes

Sur quoi vous basez-vous pour penser que c'est tout ce qu'il fait ? De plus, pouvez-vous fournir une liste des "événements système" qui sont reçus par l'enfant ? Une recherche Google pour iOS "system events" ne donne pas grand-chose ; il ne semble pas que ce soit un terme utilisé par Apple ?

0 votes

Il s'agit essentiellement d'une méthode pratique qui vous permet d'ajouter la vue du contrôleur de vue B en tant que sous-vue du contrôleur de vue A, tout en laissant le contrôleur de vue B gérer sa vue. Pour que cela fonctionne correctement, vous devez vous assurer que le contrôleur de vues B reçoit les événements système (lire les callbacks UIViewControllerDelegate). La fonction "addChildViewController" s'en charge pour vous, afin de vous épargner l'effort de tout transmettre manuellement.

0voto

Rob Points 70987

Qu'est-ce que addChildViewController font réellement ?

Il s'agit de la première étape du confinement des vues, un processus par lequel nous maintenons la hiérarchie des vues en synchronisation avec la hiérarchie des contrôleurs de vues, dans les cas où nous avons une vue secondaire qui a encapsulé sa logique dans son propre contrôleur de vues (pour simplifier le contrôleur de vues parent, pour permettre une vue enfant réutilisable avec sa propre logique, etc.)

Donc, addChildViewController ajoute le contrôleur de vue enfant à un tableau de childViewControllers qui garde la trace des enfants, leur facilite l'obtention de tous les événements de vue, conserve une référence forte à l'enfant pour vous, etc.

Mais attention, addChildViewController n'est que la première étape. Vous devez également appeler didMoveToParentViewController aussi :

- (void)showViewController:(UIViewController *)newViewController {
    UIViewController* oldViewController = [self.childViewControllers objectAtIndex:0];

    [oldViewController willMoveToParentViewController:nil];   // tell it you are going to remove the child
    [oldViewController.view removeFromSuperview];             // remove view
    [oldViewController removeFromParentViewController];       // tell it you have removed child; this calls `didMoveToParentViewController` for you

    newViewController.view.frame = self.view.bounds;          // use `bounds`, not `frame`
    newViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;  // be explicit regarding resizing mask if setting `frame`

    [self addChildViewController:newViewController];          // tell it that you are going to add a child; this calls `willMoveToParentViewController` for you
    [self.view addSubview:newViewController.view];            // add the view
    [newViewController didMoveToParentViewController:self];   // tell it that you are done
}

En passant, veuillez noter la séquence des appels, dont l'ordre est important. Lors de l'ajout, par exemple, vous appelez addChildViewController , addSubview y didMoveToParentViewController dans cet ordre. Notez que, comme la documentation pour didMoveToParentViewController dit :

Si vous implémentez votre propre contrôleur de vue de conteneur, il doit appeler la fonction didMoveToParentViewController: du contrôleur de vue enfant une fois la transition vers le nouveau contrôleur terminée ou, s'il n'y a pas de transition, immédiatement après avoir appelé la méthode addChildViewController: méthode.

Et, si vous vous demandez pourquoi nous n'appelons pas willMoveToParentViewController dans ce cas aussi, c'est parce que, comme la documentation dit, addChildViewController fait cela pour vous :

Lorsque votre conteneur personnalisé appelle la fonction addChildViewController: il appelle automatiquement la méthode willMoveToParentViewController: du contrôleur de vue à ajouter comme enfant avant de l'ajouter.

De même, lors de la suppression, vous appelez willMoveToParentViewController , removeFromSuperview y removeFromParentViewController . Comme la documentation pour willMoveToParentViewController dit :

Si vous implémentez votre propre contrôleur de vue de conteneur, il doit appeler la fonction willMoveToParentViewController: du contrôleur de vue enfant avant d'appeler la méthode removeFromParentViewController en passant par une valeur parentale de nil .

Et, encore une fois, si vous vous demandez pourquoi nous n'appelons pas didMoveToParentViewController lors du retrait de l'enfant, c'est parce que, en tant que la documentation dit, removeFromParentViewController fait cela pour vous :

El removeFromParentViewController appelle automatiquement la méthode didMoveToParentViewController: du contrôleur de vue enfant après avoir supprimé l'enfant.

Pour info, si vous animez la suppression de la sous-vue, mettez l'appel à removeFromParentViewController dans le gestionnaire d'achèvement de l'animation.

Mais si vous effectuez la séquence correcte d'appels de confinement, décrite ci-dessus, l'enfant recevra tous les événements appropriés liés à la vue.

Pour plus d'informations (en particulier, pourquoi ces willMoveToParentViewController y didMoveToParentViewController les appels sont si importants), voir la vidéo de la WWDC 2011 Mise en œuvre du confinement de UIViewController . Voir également le Mise en œuvre d'un contrôleur de vue de conteneur de la section UIViewController documentation .


À titre d'observation mineure, assurez-vous que, lorsque vous ajoutez la vue de l'enfant en tant que sous-vue, vous faites référence à l'attribut bounds de la vue du contrôleur de vue parent, et non de la vue frame . Le site frame de la vue du parent est dans le système de coordonnées de sa vue d'ensemble . Le site bounds est dans son propre système de coordonnées.

Vous ne remarquerez peut-être pas la différence lorsque la vue du parent occupe tout l'écran, mais dès que vous utiliserez cette méthode dans un scénario où la vue du parent n'occupe pas tout l'écran, vous commencerez à rencontrer un désalignement des images. Utilisez toujours bounds lors de la mise en place de coordonnées pour les enfants. (Ou bien utilisez les contraintes, ce qui vous permet d'échapper complètement à cette absurdité).


Il n'est peut-être pas nécessaire de dire que si vous souhaitez simplement ajouter l'enfant lorsque le parent est instancié, vous pouvez effectuer le confinement du contrôleur de vue entièrement dans les storyboards sans aucun de ces éléments. add / remove y willMove / didMove appels du tout. Il suffit d'utiliser "Container View" et de transmettre toutes les données nécessaires à l'enfant pendant l'initialisation en utilisant prepareForSegue .

enter image description here

Par exemple, si le parent a une propriété appelée bar et vous vouliez mettre à jour une propriété appelée baz dans l'enfant :

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.destinationViewController isKindOfClass:[ChildViewController class]]) {
        ChildViewController *destination = segue.destinationViewController;

        destination.baz = self.bar;
    }
}

Maintenant, si vous voulez ajouter/supprimer des enfants de manière programmatique, utilisez la méthode décrite ci-dessus. Mais le storyboard "Container View" peut gérer tous les appels de confinement de vue pour les scénarios simples avec très peu de code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X