Que fait réellement addChildViewController?

102

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

Conformément aux instructions de la documentation Apple, j'appelle addChildViewControllerchaque fois que j'ajoute un ViewController enfant à mon conteneur. Mon code pour échanger le contrôleur de vue enfant actuel affiché par le SideBarViewControllerressemble à ceci:

- (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];
}

Ensuite, j'ai commencé à essayer de comprendre ce que addChildViewControllerfait ici et j'ai réalisé que je n'en avais aucune idée. En plus de coller le nouveau ViewControllerdans le .childViewControllerstableau, il semble n'avoir aucun effet sur quoi que ce soit. Les actions et les prises de la vue du contrôleur enfant vers le contrôleur enfant que j'ai défini sur le storyboard fonctionnent toujours très bien même si je n'appelle jamais addChildViewController, et je ne peux pas imaginer ce que cela pourrait affecter d'autre.

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

- (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 sache!

La documentation Apple n'éclaire pas beaucoup sur ce que addChildViewControllerfait, ou pourquoi nous sommes censés l'appeler. Toute l'étendue de la description pertinente de ce que fait la méthode ou pourquoi elle devrait être utilisée dans sa section dans la UIViewControllerréférence de classe est, à l'heure actuelle:

Ajoute le contrôleur de vue donné en tant qu'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 remplacez 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 de vues. Cela permet à iOS d'acheminer correctement les événements vers les contrôleurs de vue enfants 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 créer ou annuler 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; ils doivent être utilisés 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) appeler ces méthodes est «essentiel».

Les exemples de contrôleurs de vue de conteneur personnalisés dans la section "Contrôleurs de vue de conteneur personnalisés" de la documentation Apple appellent tous cette méthode, donc je suppose qu'elle sert un objectif important au-delà de simplement faire apparaître le ViewController enfant sur un tableau, mais je ne peux pas comprendre quel est cet objectif. Que fait cette méthode et pourquoi devrais-je l'appeler?

Mark Amery
la source
3
La page de vidéos de la WWDC 2011 d'Apple propose une excellente session («Implémentation du confinement UIViewController») sur ce sujet.
Alladinian

Réponses:

94

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

viewWillAppear:, viewDidAppear:, Etc ont rien à voir avec addChildViewController:. Tout ce que cela addChildViewController:fait 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 entrent et sortent de la hiérarchie des fenêtres.

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

  • Transfert des méthodes d'apparence vers les contrôleurs de vue enfants
  • Méthodes de rotation de transfert
  • (Éventuellement) transmission des avertissements de mémoire
  • Éviter les hiérarchies de VC incohérentes, en particulier transitionFromViewController:toViewController:…lorsque les deux VC doivent avoir le même parent
  • Permettre aux contrôleurs de vue de conteneur personnalisés de participer à la préservation et à la restauration de l'état
  • Participer à la chaîne des répondeurs
  • Accrocher les navigationController, tabBarControllerpropriétés , etc.
roi nevan
la source
C'est la session 102 pas 101
SeanChense
+1 pour la chaîne de répondeurs. addChildViewController est requis si vous souhaitez recevoir des événements tactiles sur une sous-vue appartenant à un enfant UIViewController
charlieb
108

Je pense qu'un exemple vaut mille mots.

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

entrez la description de l'image ici

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 NotepadViewControlleret ajoute sa vue racine en tant que sous-vue à la vue principale. Jusqu'ici tout va bien.

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

entrez la description de l'image ici

J'ai donc voulu changer l'image du bloc-notes et la décaler. Et pour ce faire, j'ai écrit le code approprié dans la willAnimateRotationToInterfaceOrientation:duration:méthode, mais lorsque j'ai exécuté l'application, rien ne s'est passé! Et après le débogage, j'ai remarqué qu'aucune des UIViewControllerméthodes de rotation de s 'est réellement appelée NotepadViewController. Seules ces 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 NotepadViewControllermanuellement lorsqu'elles sont appelées dans le contrôleur de vue principal. Cela compliquera bientôt les choses et créera une dépendance supplémentaire entre les composants non liés de l'application.

C'était dans le passé, avant l'introduction du concept de contrôleurs de vue enfants. Mais maintenant, il vous suffit d' addChildViewControlleraccéder au contrôleur de vue principal et tout fonctionnera comme prévu sans plus de travail manuel.

Modifier: il existe deux catégories d'événements qui sont transférés aux contrôleurs de vue enfants:

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 transférer automatiquement en remplaçant shouldAutomaticallyForwardRotationMethodset shouldAutomaticallyForwardAppearanceMethods.

Hejazi
la source
D'après la documentation et après avoir fait un test rapide, je ne pense pas qu'il y ait un autre événement qui n'est transmis que si vous addChildViewControllerau contrôleur parent.
Hejazi
souhaite qu'il soit automatiquement transféré viewWillLayoutSubviews
MobileMon
10

-[UIViewController addChildViewController:]ajoute uniquement le contrôleur de vue passé dans un tableau de viewControllers dont un viewController (le parent) souhaite conserver la référence. Vous devez en fait ajouter vous-même les vues de viewController à l'écran en les ajoutant en tant que sous-vues d'une autre vue (par exemple la vue de parentViewController). Il existe également un objet pratique dans Interface Builder pour utiliser childrenViewControllers dans les storyboards.

Auparavant, pour garder la référence des autres viewControllers dont vous utilisiez les vues, vous deviez en garder une référence manuelle dans @properties. Avoir une propriété intégrée comme childViewControllerset par conséquent parentViewControllerest un moyen pratique de gérer de telles interactions et de créer des viewControllers composés tels que UISplitViewController que vous trouvez sur les applications iPad.

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

C'est tout.

Gianluca Tranchedone
la source
Quelle est votre base pour penser que c'est tout ce qu'il fait? Pouvez-vous également fournir une liste des «événements système» reçus par l'enfant? Une recherche sur Google iOS "system events"ne jette pas grand-chose; cela ne semble pas être un terme utilisé par Apple?
Mark Amery
Il s'agit essentiellement d'une méthode pratique qui vous permet d'ajouter la vue de View Controller B en tant que sous-vue de View Controller A, tout en laissant View Controller B gérer sa vue. Pour que cela fonctionne correctement, vous devez vous assurer que View Controller B obtient les événements système (lisez les rappels UIViewControllerDelegate). 'addChildViewController' connecte cela pour vous, pour vous éviter l'effort de tout transmettre manuellement.
Sam Clewlow