¿Qué hace realmente addChildViewController?

102

Acabo de sumergirme por primera vez en el desarrollo de iOS, y una de las primeras cosas que tuve que hacer es implementar un controlador de vista de contenedor personalizado , llamémoslo SideBarViewController, que intercambia cuál de varios posibles controladores de vista infantil es muestra, casi exactamente como un controlador de barra de pestañas estándar . (Es más o menos un controlador de barra de pestañas pero con un menú lateral que se puede ocultar en lugar de una barra de pestañas).

Según las instrucciones de la documentación de Apple, llamo addChildViewControllercada vez que agrego un ViewController secundario a mi contenedor. Mi código para cambiar el controlador de vista infantil actual se muestra SideBarViewControllerasí:

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

Luego comencé a tratar de averiguar qué addChildViewControllerhace aquí, y me di cuenta de que no tenía ni idea. Además de colocar lo nuevo ViewControlleren la .childViewControllersmatriz, parece que no tiene ningún efecto en nada. Las acciones y salidas desde la vista del controlador infantil al controlador infantil que configuré en el guión gráfico todavía funcionan bien incluso si nunca llamo addChildViewController, y no puedo imaginar qué más podría afectar.

De hecho, si reescribo mi código para no llamar addChildViewControllery, en cambio, me veo así ...

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

... entonces mi aplicación todavía funciona perfectamente, ¡hasta donde yo sé!

La documentación de Apple no arroja mucha luz sobre qué lo addChildViewControllerhace, o por qué se supone que debemos llamarlo. El alcance completo de la descripción relevante de lo que hace el método o por qué debería usarse en su sección en la UIViewControllerReferencia de clase es, en la actualidad:

Agrega el controlador de vista dado como hijo. ... Este método solo está destinado a ser llamado por una implementación de un controlador de vista de contenedor personalizado. Si anula este método, debe llamar a super en su implementación.

También está este párrafo anterior en la misma página:

Su controlador de vista de contenedor debe asociar un controlador de vista secundario consigo mismo antes de agregar la vista raíz del niño a la jerarquía de vistas. Esto permite que iOS enrute correctamente los eventos a los controladores de vista infantil y las vistas que administran esos controladores. Del mismo modo, después de eliminar la vista raíz de un niño de su jerarquía de vistas, debería desconectar ese controlador de vista secundario de sí mismo. Para hacer o deshacer estas asociaciones, su contenedor llama a métodos específicos definidos por la clase base. Estos métodos no están destinados a ser llamados por clientes de su clase de contenedor; solo deben ser utilizados por la implementación de su contenedor para proporcionar el comportamiento de contención esperado.

Estos son los métodos esenciales a los que puede necesitar llamar:

addChildViewController:
removeFromParentViewController
willMoveToParentViewController:
didMoveToParentViewController:

pero no ofrece ninguna pista sobre cuáles son los 'eventos' o el 'comportamiento de contención esperado' de los que está hablando, o por qué (o incluso cuándo) llamar a estos métodos es 'esencial'.

Todos los ejemplos de controladores de vista de contenedor personalizados en la sección "Controladores de vista de contenedor personalizados" de la documentación de Apple llaman a este método, así que supongo que tiene un propósito importante más allá de simplemente colocar el ViewController secundario en una matriz, pero no puedo entender averiguar cuál es ese propósito. ¿Qué hace este método y por qué debería llamarlo?

Mark Amery
fuente
3
La página de videos de la WWDC 2011 de Apple tiene una gran sesión ("Implementación de la contención de UIViewController") sobre este tema.
Alladiniano

Respuestas:

94

Yo también me estaba preguntando sobre esta pregunta. Vi la Sesión 102 de los videos de la WWDC 2011 y el Sr. View Controller, Bruce D. Nilo , dijo esto:

viewWillAppear:, viewDidAppear:, Etc no tienen nada que ver con addChildViewController:. Todo lo que addChildViewController:hace es decir "Este controlador de vista es un hijo de ese" y no tiene nada que ver con la apariencia de la vista. Cuando se les llama está asociado con el momento en que las vistas entran y salen de la jerarquía de ventanas.

Entonces parece que la llamada a addChildViewController:hace muy poco. Los efectos secundarios de la llamada son la parte importante. Vienen de las relaciones parentViewControllery childViewControllers. Estos son algunos de los efectos secundarios que conozco:

  • Reenvío de métodos de apariencia a controladores de vista infantil
  • Métodos de rotación de reenvío
  • (Posiblemente) reenvío de advertencias de memoria
  • Evitar jerarquías de VC inconsistentes, especialmente transitionFromViewController:toViewController:…cuando ambos VC necesitan tener el mismo padre
  • Permitir que los controladores de vista de contenedores personalizados participen en la preservación y restauración del estado
  • Participar en la cadena de respuesta
  • Conexión de las navigationController, tabBarControllerpropiedades, etc.
rey nevan
fuente
Es la sesión 102, no la 101
SeanChense
+1 para la cadena de respuesta. addChildViewController es necesario si desea recibir eventos táctiles en una subvista propiedad de un niño UIViewController
charlieb
108

Creo que un ejemplo vale más que mil palabras.

Estaba trabajando en una aplicación de biblioteca y quería mostrar una bonita vista de bloc de notas que aparece cuando el usuario quiere agregar una nota.

ingrese la descripción de la imagen aquí

Después de probar algunas soluciones, terminé inventando mi propia solución personalizada para mostrar el bloc de notas. Entonces, cuando quiero mostrar el bloc de notas, creo una nueva instancia de NotepadViewControllery agrego su vista raíz como una subvista a la vista principal. Hasta aquí todo bien.

Luego noté que la imagen del bloc de notas está parcialmente oculta debajo del teclado en modo horizontal.

ingrese la descripción de la imagen aquí

Así que quería cambiar la imagen del bloc de notas y subirla. Y para hacerlo, escribí el código adecuado en el willAnimateRotationToInterfaceOrientation:duration:método, ¡pero cuando ejecuté la aplicación no pasó nada! Y después de la depuración, noté que ninguno de UIViewControllerlos métodos de rotación de 'está realmente llamado NotepadViewController. Solo se están llamando a esos métodos en el controlador de vista principal.

Para resolver esto, necesitaba llamar a todos los métodos NotepadViewControllermanualmente cuando se llaman en el controlador de vista principal. Esto pronto complicará las cosas y creará una dependencia adicional entre componentes no relacionados en la aplicación.

Eso fue en el pasado, antes de que se introdujera el concepto de controladores de vista infantil. Pero ahora, solo necesita acceder addChildViewControlleral controlador de vista principal y todo funcionará como se espera sin más trabajo manual.

Editar: hay dos categorías de eventos que se reenvían a los controladores de vista infantil:

1- Métodos de aparición:

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

2- Métodos de rotación:

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

También puede controlar qué categorías de eventos desea que se reenvíen automáticamente anulando shouldAutomaticallyForwardRotationMethodsy shouldAutomaticallyForwardAppearanceMethods.

Hejazi
fuente
De la documentación y después de hacer una prueba rápida, no creo que haya ningún otro evento que solo se reenvíe si usted addChildViewControlleral controlador principal.
Hejazi
deseo que se reenvíe automáticamente viewWillLayoutSubviews
MobileMon
10

-[UIViewController addChildViewController:]solo agrega el controlador de vista pasado en una matriz de viewControllers que un viewController (el padre) quiere mantener la referencia. En realidad, debería agregar esas vistas de viewController en la pantalla usted mismo agregándolas como subvistas de otra vista (por ejemplo, la vista de parentViewController). También hay un objeto de conveniencia en Interface Builder para usar childrenViewControllers en Storyboards.

Anteriormente, para mantener la referencia de otros viewControllers de los que usaba las vistas, tenía que mantener la referencia manual de ellos en @properties. Tener una propiedad incorporada como childViewControllersy, en consecuencia, parentViewControlleres una forma conveniente de administrar tales interacciones y crear viewControllers compuestos como el UISplitViewController que se encuentra en las aplicaciones de iPad.

Además, childrenViewControllers también reciben automáticamente todos los eventos del sistema que recibe el padre: -viewWillAppear, -viewWillDisappear, etc. Anteriormente, debería haber llamado a estos métodos manualmente en sus "childrenViewControllers".

Eso es.

Gianluca Tranchedone
fuente
¿Cuál es tu base para pensar que eso es todo lo que hace? Además, ¿puede proporcionar una lista de los 'eventos del sistema' que recibe el niño? Una búsqueda en Google de iOS "system events"no arroja mucho; ¿No parece ser un término que usa Apple?
Mark Amery
Es básicamente un método de conveniencia que le permite agregar la vista del Controlador de vista B como una subvista del Controlador de vista A, pero aún así hacer que el Controlador de vista B administre su vista. Para que esto funcione correctamente, debe asegurarse de que View Controller B reciba los eventos del sistema (lea las devoluciones de llamada UIViewControllerDelegate). 'addChildViewController' conecta esto para usted, para ahorrarle el esfuerzo de pasar todo manualmente.
Sam Clewlow