Implementación de la contención de contenedores: controlador de menú deslizante
Spanish (Español) translation by Juliana Carvajal (you can also view the original English article)
En este tutorial, implementaremos una versión minimalista de la interfaz de usuario estilo Facebook/Path. El objetivo será comprender cómo utilizar la contención del controlador de vista para implementar un flujo personalizado en tu aplicación.
Resumen teórico
Los controladores de vista son una parte esencial de cualquier aplicación de iOS, sin importar cuán pequeños, grandes, simples o complejos sean. Proporcionan la "lógica de unión" entre el modelo de datos de tu aplicación y la interfaz de usuario.
En términos generales, hay dos tipos de controladores de vista:
- Controladores de vista de contenido: estos son responsables de mostrar y administrar el contenido visible.
- Controladores de contenedor: estos administran los controladores de vista de contenido y son responsables de la estructura general y el flujo de la aplicación.
Un controlador de contenedor puede tener algún componente visible propio, pero básicamente funciona como un anfitrión para los controladores de vista de contenido. Los controladores de contenedor sirven para "traficar" las idas y venidas de los controladores de visualización de contenido.
UINavigationController, UITabBarController y UIPageViewController son ejemplos de controladores de vista de contenedor que se envían con el SDK de iOS. Considera en qué se diferencian los tres en términos de los flujos de aplicación a los que dan lugar. El controlador de navegación es ideal para una aplicación de tipo detallado, donde la selección que hace el usuario en una pantalla afecta las opciones que se te presentan en la siguiente pantalla. El controlador de la barra de pestañas es ideal para aplicaciones con funciones independientes, lo que permite alternar cómodamente con solo presionar un botón de pestaña. Finalmente, el controlador de vista de página presenta una metáfora de libro, lo que permite al usuario pasar de una página a otra de contenido.
Lo clave a tener en cuenta aquí es que una pantalla real de contenido que se presenta a través de cualquiera de estos controladores de vista de contenedor debe administrarse, tanto en términos de los datos de los que se deriva (el modelo) como de la presentación en pantalla. (la vista), que nuevamente sería el trabajo de un controlador de vista. Ahora estamos hablando de controladores de vista de contenido. En algunas aplicaciones, particularmente en el iPad porque su pantalla más grande permite que se muestre más contenido a la vez, es posible que incluso sea necesario administrar diferentes vistas en la pantalla de forma independiente. Esto requiere varios controladores de vista en pantalla a la vez. Todo esto implica que los controladores de vista en una aplicación bien diseñada deben implementarse de manera jerárquica con los controladores de vista de contenido y de contenedor desempeñando sus respectivos roles.
Antes de iOS 5, no había forma de declarar una relación jerárquica (es decir, padre-hijo) entre dos controladores de vista y, por lo tanto, no había una forma "adecuada" de implementar un flujo de aplicación personalizado. Uno tenía que conformarse con los tipos integrados, o hacerlo de manera aleatoria, que básicamente consistía en pegar vistas administradas por un controlador de vista en la jerarquía de vista de la vista administrada por otro controlador de vista. Esto crearía inconsistencias. Por ejemplo, una vista terminaría estando en la jerarquía de vista de dos controladores sin que ninguno de estos controladores reconozca al otro, lo que a veces conduce a un comportamiento extraño. La contención se introdujo en iOS 5 y se perfeccionó ligeramente en iOS 6, y permite formalizar la noción de controladores de vista padre e hijo en una jerarquía. Esencialmente, la contención correcta del controlador de vista exige que si la vista B es una subvista (secundaria) de la vista A y si no están bajo la administración del mismo controlador de vista, entonces el controlador de vista de B debe convertirse en hijo del controlador de vista de A.
Podrías preguntar si hay algún beneficio concreto que ofrece la contención del controlador de vista además de la ventaja del diseño jerárquico que discutimos. La respuesta es sí. Ten en cuenta que cuando un controlador de vista aparece en la pantalla o desaparece, es posible que necesitemos configurar o eliminar recursos, limpiar, recuperar o guardar información desde/hacia el sistema de archivos. Todos conocemos las devoluciones de llamada de apariencia. Al declarar explícitamente la relación padre-hijo, nos aseguramos de que el controlador padre reenvíe las devoluciones de llamada a sus hijos cada vez que uno entra o sale de la pantalla. Las devoluciones de llamada de rotación también deben reenviarse. Cuando cambias la orientación, todos los controladores de vista en la pantalla deben saberlo para poder ajustar su contenido de manera adecuada.
¿Qué implica todo esto, en términos de código? Los controladores de vista tienen una propiedad NSArray llamada childViewControllers y nuestras responsabilidades incluyen agregar y eliminar controladores de vista secundarios hacia y desde esta matriz en el padre llamando a los métodos apropiados. Estos métodos incluyen addChildViewController (llamado en el padre) y removeFromParentViewController (llamado en el hijo) cuando buscamos establecer o romper la relación padre-hijo. También se envían un par de mensajes de notificación al controlador de vista infantil al inicio y al final del proceso de adición/eliminación. Estos son willMoveToParentViewController: y didMoveToParentViewController:, enviados con el controlador padre apropiado como argumento. El argumento es nulo si se elimina al niño. Como veremos, uno de estos mensajes se nos enviará automáticamente mientras que el otro será nuestra responsabilidad. Esto dependerá de si agregamos o eliminamos al niño. Estudiaremos la secuencia exacta en breve cuando implementemos cosas en el código. El controlador secundario puede responder a estas notificaciones implementando los métodos correspondientes si necesitas hacer algo en preparación de estos eventos.
También necesitamos agregar/eliminar las vistas asociadas con el controlador de vista secundario a la jerarquía del padre, utilizando métodos como addSubview: o removeFromSuperview), incluida la realización de las animaciones que lo acompañan. Hay un método de conveniencia (-)transitionFromViewController:toViewController:duration:options:animations:completion: que nos permite agilizar el proceso de intercambio de controladores de vista infantil en pantalla con animaciones. Veremos los detalles exactos cuando escribamos el código, ¡cuál es el siguiente!
1. Creación de un proyecto nuevo
Crea una nueva aplicación iOS en Xcode basada en la plantilla "Aplicación vacía". Conviértelo en una aplicación de iOS con ARC habilitado. Llámalo VCContainmentTut.



2. Implementación del controlador de vista de contenedor
Crea una nueva clase llamada RootController. Conviértelo en una subclase de UIViewController. Asegúrate de que las casillas de verificación estén deseleccionadas. Esta será nuestra subclase de controlador de vista de contenedor.



Reemplaza el código en RootViewController.h con lo siguiente.
1 |
#import <UIKit/UIKit.h>
|
2 |
|
3 |
@interface RootController : UIViewController<UITableViewDataSource, UITableViewDelegate> // (1) |
4 |
|
5 |
- (id)initWithViewControllers:(NSArray *)viewControllers andMenuTitles:(NSArray *)titles; // (2) |
6 |
|
7 |
@end
|
Nuestro controlador de contenedor tendrá una vista de tabla que funciona como nuestro menú, y al tocar cualquier celda se reemplazará el controlador de vista actualmente visible por el seleccionado a través del toque del usuario.
Refiriéndote a los puntos en el código,
- Nuestro controlador raíz funciona como el delegado y la fuente de datos del menú (es decir, la vista de tabla).
- Estamos ofreciendo una API extremadamente simple (en lo que respecta a nuestro usuario de clase de contenedor) que consiste en un inicializador que toma una lista de controladores de vista que nuestro controlador raíz debe contener, y una lista de cadenas que representan los títulos para cada controlador de vista en el menú. Esto es para que podamos concentrarnos en lo básico. Una vez que los comprendas, puedes hacer que la API sea tan personalizable como desees.
Echemos un vistazo hacia adelante para ver cómo se verá nuestro producto terminado para que tengas una imagen mental con la que asociar la implementación.

Será útil darse cuenta de que nuestro controlador de vista de contenedor es bastante similar a un controlador de vista de pestaña. Cada elemento del menú corresponde a un controlador de vista independiente. La diferencia entre nuestro controlador de "menú deslizante" y el controlador de pestañas es visual en su mayor parte. La frase "menú deslizante" es un nombre poco apropiado porque en realidad es el controlador de vista de contenido el que se desliza para ocultar o revelar el menú que se encuentra debajo.
Pasando a la implementación, reemplaza todo el código en RootController.m con el siguiente código.
1 |
#define kExposedWidth 200.0
|
2 |
#define kMenuCellID @"MenuCell"
|
3 |
|
4 |
#import "RootController.h"
|
5 |
|
6 |
@interface RootController() |
7 |
|
8 |
@property (nonatomic, strong) UITableView *menu; |
9 |
@property (nonatomic, strong) NSArray *viewControllers; |
10 |
@property (nonatomic, strong) NSArray *menuTitles; |
11 |
|
12 |
@property (nonatomic, assign) NSInteger indexOfVisibleController; |
13 |
|
14 |
@property (nonatomic, assign) BOOL isMenuVisible; |
15 |
|
16 |
@end
|
17 |
|
18 |
|
19 |
@implementation RootController |
20 |
|
21 |
- (id)initWithViewControllers:(NSArray *)viewControllers andMenuTitles:(NSArray *)menuTitles |
22 |
{
|
23 |
if (self = [super init]) |
24 |
{
|
25 |
NSAssert(self.viewControllers.count == self.menuTitles.count, @"There must be one and only one menu title corresponding to every view controller!"); // (1) |
26 |
NSMutableArray *tempVCs = [NSMutableArray arrayWithCapacity:viewControllers.count]; |
27 |
|
28 |
self.menuTitles = [menuTitles copy]; |
29 |
|
30 |
for (UIViewController *vc in viewControllers) // (2) |
31 |
{
|
32 |
if (![vc isMemberOfClass:[UINavigationController class]]) |
33 |
{
|
34 |
[tempVCs addObject:[[UINavigationController alloc] initWithRootViewController:vc]]; |
35 |
}
|
36 |
else
|
37 |
[tempVCs addObject:vc]; |
38 |
|
39 |
UIBarButtonItem *revealMenuBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Menu" style:UIBarButtonItemStylePlain target:self action:@selector(toggleMenuVisibility:)]; // (3) |
40 |
|
41 |
UIViewController *topVC = ((UINavigationController *)tempVCs.lastObject).topViewController; |
42 |
topVC.navigationItem.leftBarButtonItems = [@[revealMenuBarButtonItem] arrayByAddingObjectsFromArray:topVC.navigationItem.leftBarButtonItems]; |
43 |
|
44 |
|
45 |
}
|
46 |
self.viewControllers = [tempVCs copy]; |
47 |
self.menu = [[UITableView alloc] init]; // (4) |
48 |
self.menu.delegate = self; |
49 |
self.menu.dataSource = self; |
50 |
|
51 |
}
|
52 |
return self; |
53 |
}
|
54 |
|
55 |
|
56 |
|
57 |
- (void)viewDidLoad |
58 |
{
|
59 |
[super viewDidLoad]; |
60 |
[self.menu registerClass:[UITableViewCell class] forCellReuseIdentifier:kMenuCellID]; |
61 |
self.menu.frame = self.view.bounds; |
62 |
[self.view addSubview:self.menu]; |
63 |
|
64 |
self.indexOfVisibleController = 0; |
65 |
UIViewController *visibleViewController = self.viewControllers[0]; |
66 |
visibleViewController.view.frame = [self offScreenFrame]; |
67 |
[self addChildViewController:visibleViewController]; // (5) |
68 |
[self.view addSubview:visibleViewController.view]; // (6) |
69 |
self.isMenuVisible = YES; |
70 |
[self adjustContentFrameAccordingToMenuVisibility]; // (7) |
71 |
|
72 |
|
73 |
[self.viewControllers[0] didMoveToParentViewController:self]; // (8) |
74 |
|
75 |
}
|
76 |
|
77 |
- (void)toggleMenuVisibility:(id)sender // (9) |
78 |
{
|
79 |
self.isMenuVisible = !self.isMenuVisible; |
80 |
[self adjustContentFrameAccordingToMenuVisibility]; |
81 |
}
|
82 |
|
83 |
|
84 |
- (void)adjustContentFrameAccordingToMenuVisibility // (10) |
85 |
{
|
86 |
UIViewController *visibleViewController = self.viewControllers[self.indexOfVisibleController]; |
87 |
CGSize size = visibleViewController.view.frame.size; |
88 |
|
89 |
if (self.isMenuVisible) |
90 |
{
|
91 |
[UIView animateWithDuration:0.5 animations:^{ |
92 |
visibleViewController.view.frame = CGRectMake(kExposedWidth, 0, size.width, size.height); |
93 |
}];
|
94 |
}
|
95 |
else
|
96 |
[UIView animateWithDuration:0.5 animations:^{ |
97 |
visibleViewController.view.frame = CGRectMake(0, 0, size.width, size.height); |
98 |
}];
|
99 |
|
100 |
}
|
101 |
|
102 |
- (void)replaceVisibleViewControllerWithViewControllerAtIndex:(NSInteger)index // (11) |
103 |
{
|
104 |
if (index == self.indexOfVisibleController) return; |
105 |
UIViewController *incomingViewController = self.viewControllers[index]; |
106 |
incomingViewController.view.frame = [self offScreenFrame]; |
107 |
UIViewController *outgoingViewController = self.viewControllers[self.indexOfVisibleController]; |
108 |
CGRect visibleFrame = self.view.bounds; |
109 |
|
110 |
|
111 |
[outgoingViewController willMoveToParentViewController:nil]; // (12) |
112 |
|
113 |
[self addChildViewController:incomingViewController]; // (13) |
114 |
[[UIApplication sharedApplication] beginIgnoringInteractionEvents]; // (14) |
115 |
[self transitionFromViewController:outgoingViewController // (15) |
116 |
toViewController:incomingViewController |
117 |
duration:0.5 options:0 |
118 |
animations:^{ |
119 |
outgoingViewController.view.frame = [self offScreenFrame]; |
120 |
|
121 |
}
|
122 |
|
123 |
completion:^(BOOL finished) { |
124 |
[UIView animateWithDuration:0.5 |
125 |
animations:^{ |
126 |
[outgoingViewController.view removeFromSuperview]; |
127 |
[self.view addSubview:incomingViewController.view]; |
128 |
incomingViewController.view.frame = visibleFrame; |
129 |
[[UIApplication sharedApplication] endIgnoringInteractionEvents]; // (16) |
130 |
}];
|
131 |
[incomingViewController didMoveToParentViewController:self]; // (17) |
132 |
[outgoingViewController removeFromParentViewController]; // (18) |
133 |
self.isMenuVisible = NO; |
134 |
self.indexOfVisibleController = index; |
135 |
}];
|
136 |
}
|
137 |
|
138 |
|
139 |
// (19):
|
140 |
|
141 |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView |
142 |
{
|
143 |
return 1; |
144 |
}
|
145 |
|
146 |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section |
147 |
{
|
148 |
return self.menuTitles.count; |
149 |
}
|
150 |
|
151 |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath |
152 |
{
|
153 |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kMenuCellID]; |
154 |
cell.textLabel.text = self.menuTitles[indexPath.row]; |
155 |
return cell; |
156 |
}
|
157 |
|
158 |
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath |
159 |
{
|
160 |
[self replaceVisibleViewControllerWithViewControllerAtIndex:indexPath.row]; |
161 |
}
|
162 |
|
163 |
- (CGRect)offScreenFrame |
164 |
{
|
165 |
return CGRectMake(self.view.bounds.size.width, 0, self.view.bounds.size.width, self.view.bounds.size.height); |
166 |
}
|
167 |
|
168 |
@end
|
Ahora para una explicación del código. Las partes que he resaltado para enfatizar son especialmente relevantes para la implementación de la contención.
- Primero, el inicializador realiza una verificación simple para asegurarse de que a cada controlador de vista se le haya asignado un título de menú. No hemos realizado ninguna verificación de tipo para asegurarnos de que cada una de las dos matrices pasadas al inicializador contenga el tipo correcto de objetos, los tipos
UIViewControlleryNSStringrespectivamente. Podrías considerar hacerlo. Ten en cuenta que mantenemos matrices para cada uno de estos, llamadosviewControllersymenuTitles. - Queremos que haya un botón que, al tocarlo, muestre u oculte el menú. Mi solución simple en cuanto a dónde debería estar el botón fue colocar cada controlador de vista que recibimos del inicializador dentro de un controlador de navegación. Esto nos da una barra de navegación gratuita a la que se puede agregar un botón, a menos que el controlador de vista pasado ya sea un controlador de navegación, en cuyo caso no hacemos nada adicional.
- Creamos un elemento de botón de barra que activa la apariencia del menú o se oculta deslizando el controlador de vista que está visible actualmente. Lo agregamos a la barra de navegación moviendo cualquier botón existente en la barra de navegación hacia la derecha. Hacemos esto porque el controlador de vista agregado ya es un controlador de navegación con botones de barra preexistentes.
- Instanciamos nuestro menú como una vista de tabla y asignamos el controlador raíz como delegado y fuente de datos.
- En
viewDidLoad, después de configurar y agregar la vista de la tabla de menú a la vista de nuestro controlador raíz, introducimos en nuestra aplicación el primer controlador de vista en la matrizviewControllers. Al enviar el mensajeaddChildViewController:a nuestro controlador raíz, llevamos a cabo nuestra primera responsabilidad relacionada con la contención. Debes saber que esto hace que se llame al mensajewillMoveToParentViewController:en el controlador secundario. - ¡Ten en cuenta que necesitamos agregar explícitamente la vista de nuestro controlador secundario a la jerarquía de vista de los padres!
- Configuramos el menú para que sea visible inicialmente y llamamos a un método que ajusta el marco del controlador de la vista de contenido visible, teniendo en cuenta la visibilidad del menú. Veremos los detalles de este método en breve.
- Una vez que la vista del controlador de la vista secundaria se sienta cómodamente en la jerarquía de la vista principal, enviamos el mensaje didMoveToParentViewController al controlador secundario agregado, con
self, la instancia de RootController, como argumento. En nuestro controlador secundario, podemos implementar este método si es necesario. - Un método simple conectado a la acción de nuestro botón de la barra de menú que cambia la visibilidad del menú ajustando la vista del controlador de vista superpuesta de manera apropiada.
- Como su nombre lo indica,
adjustContentFrameAccordingToMenuVisibilitynos permite ajustar el marco del controlador de la vista de contenido para decirnos si el menú está oculto o no. Si es así, se superpone a la supervista. De lo contrario,kExposedWidthlo desplaza hacia la derecha. Lo he puesto en 200 puntos. - Nuevamente, como lo indica claramente el nombre,
replaceVisibleViewControllerWithViewControllerAtIndexnos permite intercambiar los controladores de vista y las vistas correspondientes de la jerarquía. Para llevar a cabo nuestra animación, que consiste en deslizar el controlador de vista reemplazado fuera de la pantalla hacia la derecha y luego traer el controlador de reemplazo desde el mismo lugar, definimos algunos cuadros rectangulares. - willMoveToParentViewController con
nil. Una vez que completemos este paso, este controlador de vista dejará de recibir devoluciones de llamada de apariencia y rotación del padre. Esto tiene sentido porque ya no es una parte activa de la aplicación. - Agregamos el controlador de vista entrante como hijo al controlador raíz, similar a lo que hicimos al principio.
- Comenzamos a ignorar los eventos de interacción del usuario para permitir que nuestro controlador de vista cambie sin problemas.
- Este método de conveniencia nos permite animar la eliminación del controlador saliente y la llegada del entrante mientras realizamos la secuencia requerida de eventos involucrados en el proceso de adición y eliminación del controlador de vista infantil. Animamos la vista del VC saliente para que se deslice fuera de la pantalla hacia la derecha y, una vez completada la animación, la eliminamos de la jerarquía de vistas. Luego, animamos el controlador de vista entrante para que se deslice desde el mismo lugar fuera de la pantalla y ocupe el lugar que ocupaba previamente la vista del controlador saliente.
- Permitimos que nuestra aplicación acepte eventos de interacción entrantes, ya que nuestro cambio de controlador de vista se ha completado.
- Notificamos al controlador de vista entrante que se ha movido al controlador del contenedor enviándole el mensaje
didMoveToParentViewControllercon self como argumento. - Eliminamos el controlador saliente del controlador del contenedor enviándole el mensaje
removeFromParentViewController. Debes saber quedidMoveToParentViewController:connilcomo argumento se envía por ti. - Implementamos los métodos de protocolo de fuente de datos y delegado de la vista de tabla de menú, que son bastante sencillos. Esto incluye activar el paso de intercambio del controlador de vista (11) cuando se toca la celda de un nuevo elemento en el menú a través del método
-tableView:didSelectRowAtIndexPath:.
Es posible que hayas encontrado la secuencia de llamadas relacionadas con la contención del controlador un poco confusa. Ayuda a resumir.
-
Al agregar un controlador de vista hijo a un padre:
- Llama a
addChildViewController:en el padre con el hijo como argumento. Esto hace que el mensajewillMoveToParentViewController:se envíe al niño con el padre como argumento. - Agrega la vista del niño como una subvista de la vista de los padres.
- Llama explícitamente a
didMoveToParentViewController:en el niño con el padre como argumento.
-
Al eliminar un controlador de vista secundario de tu padre:
- Llama a
willMoveToParentViewController:en el niño connilcomo argumento. - Elimina la vista del niño de tu supervista.
- Envía
removeFromParentViewControlleral niño. Esto hace que el mensajedidMoveToParentViewControllerconnilsea el argumento que se enviará al niño en su nombre.
3. Prueba
¡Probemos los diferentes tipos de controladores de vista agregados a nuestro controlador raíz! Crea una nueva subclase de UIViewController llamada ViewController, manteniendo las opciones sin marcar.
Reemplaza el código en ViewController.m con el siguiente código.
1 |
#import "ViewController.h"
|
2 |
|
3 |
@interface ViewController () |
4 |
|
5 |
@end
|
6 |
|
7 |
@implementation ViewController |
8 |
|
9 |
- (void)willMoveToParentViewController:(UIViewController *)parent |
10 |
{
|
11 |
NSLog(@"%@ (%p) - %@", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd)); |
12 |
}
|
13 |
|
14 |
- (void)didMoveToParentViewController:(UIViewController *)parent |
15 |
{
|
16 |
NSLog(@"%@ (%p) - %@", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd)); |
17 |
}
|
18 |
|
19 |
- (void)viewWillAppear:(BOOL)animated |
20 |
{
|
21 |
[super viewWillAppear:animated]; |
22 |
NSLog(@"%@ (%p) - %@", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd)); |
23 |
}
|
24 |
|
25 |
- (void)viewDidAppear:(BOOL)animated |
26 |
{
|
27 |
[super viewDidAppear:animated]; |
28 |
NSLog(@"%@ (%p) - %@", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd)); |
29 |
}
|
30 |
|
31 |
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration |
32 |
{
|
33 |
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; |
34 |
NSLog(@"%@ (%p) - %@", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd)); |
35 |
}
|
36 |
|
37 |
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation |
38 |
{
|
39 |
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; |
40 |
NSLog(@"%@ (%p) - %@", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd)); |
41 |
}
|
42 |
@end
|
No hay nada especial en nuestro controlador de vista en sí, excepto que hemos anulado las diversas devoluciones de llamada para que podamos registrarlas siempre que nuestra instancia de ViewController se convierta en un elemento secundario de nuestro controlador raíz y se produzca un evento de aparición o rotación.
En todo el código anterior, _cmd hace referencia al selector correspondiente al método en el que se encuentra nuestra ejecución. NSStringFromSelector() lo convierte en una cadena. Esta es una manera rápida y fácil de obtener el nombre del método actual sin tener que escribirlo manualmente.
Agreguemos un controlador de navegación y un controlador de pestañas a la mezcla. Esta vez usaremos Guiones gráficos.
Crea un nuevo archivo y, en iOS > Interfaz de usuario, elige guion gráfico. Configura la familia de dispositivos en iPhone y asígnale el nombre NavStoryBoard.



Desde la biblioteca de objetos, arrastra y suelta un objeto Controlador de navegación en el lienzo. Arrastra y suelta un elemento de botón de barra en el lado izquierdo de la barra de navegación en el controlador de vista de tabla designado como "Controlador de vista raíz". Contiene la vista de tabla en el lienzo. Dale cualquier nombre. Lo he llamado "Izquierda". Su propósito es verificar el código que escribimos para hacer que el botón de ocultar/revelar de la barra de menú ocupe su lugar como el botón más a la izquierda en la barra de navegación, presionando cualquier botón ya presente hacia la derecha. Finalmente, arrastra una instancia de Controlador de vista y colócala a la derecha del controlador titulado "Controlador de vista raíz" en el lienzo.
Haz clic donde dice "Vista de tabla" en el centro del segundo controlador, y en el inspector de atributos cambia el contenido de "Prototipo dinámico" a "Celdas estáticas".



Esto hará que aparezcan tres celdas de vista de tabla estática en el generador de interfaz. Elimina todas menos una de estas celdas de vista de tabla y, mientras mantienes presionada la tecla Control, haz clic y arrastra desde la celda restante hasta el controlador de vista en el extremo derecho y suelta. Selecciona "empujar" en Selección Segue. Todo lo que esto hace es provocar una transición al controlador de vista derecho cuando toca la celda solitaria de la vista de tabla. Si lo deseas, puedes colocar un UILabel en la celda de la tabla para darle algo de texto. Tu guion gráfico debe verse similar a la foto a continuación.



Finalmente, agreguemos un controlador de barra de pestañas. Tal como lo hiciste anteriormente, crea un archivo de guion gráfico y llámalo TabStoryBoard. Arrastra y suelta un elemento del controlador de la barra de pestañas desde la biblioteca de objetos al lienzo. Viene preconfigurado con dos pestañas y, si lo deseas, puedes cambiar el color de fondo de los dos controladores de vista con pestañas haciendo clic en la vista correspondiente a cada controlador de vista y cambiando la opción "fondo" en el Inspector de atributos. De esta manera, puedes verificar que la selección del controlador de vista a través de la pestaña esté funcionando correctamente.



4. Configuración del delegado de la aplicación
Ahora es el momento de configurar todo en AppDelegate.
Reemplaza el código en AppDelegate.m con el siguiente código.
1 |
#import "AppDelegate.h"
|
2 |
#import "RootController.h"
|
3 |
#import "ViewController.h"
|
4 |
|
5 |
@implementation AppDelegate |
6 |
|
7 |
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions |
8 |
{
|
9 |
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; |
10 |
UIStoryboard *tabStoryBoard = [UIStoryboard storyboardWithName:@"TabStoryboard" bundle:nil]; |
11 |
UIStoryboard *navStoryBoard = [UIStoryboard storyboardWithName:@"NavStoryboard" bundle:nil]; |
12 |
UINavigationController *navController = [navStoryBoard instantiateViewControllerWithIdentifier:@"Nav Controller"]; |
13 |
UITabBarController *tabController = [tabStoryBoard instantiateViewControllerWithIdentifier:@"Tab Controller"]; |
14 |
ViewController *redVC, *greenVC; |
15 |
redVC = [[ViewController alloc] init]; |
16 |
greenVC = [[ViewController alloc] init]; |
17 |
|
18 |
redVC.view.backgroundColor = [UIColor redColor]; |
19 |
greenVC.view.backgroundColor = [UIColor greenColor]; |
20 |
|
21 |
RootController *menuController = [[RootController alloc] |
22 |
initWithViewControllers:@[tabController, redVC, greenVC, navController] |
23 |
andMenuTitles:@[@"Tab", @"Red", @"Green", @"Nav"]]; |
24 |
self.window.rootViewController = menuController; |
25 |
self.window.backgroundColor = [UIColor whiteColor]; |
26 |
[self.window makeKeyAndVisible]; |
27 |
return YES; |
28 |
}
|
Todo lo que hicimos fue crear instancias de ViewController e instanciar la navegación y el controlador de pestañas de los dos guiones gráficos. Los pasamos en una matriz a la instancia de nuestro RootController. Ese es el controlador de contenedores que implementamos al principio. Hicimos esto junto con una matriz de cadenas para nombrar los controladores de vista en el menú. Ahora simplemente designaremos nuestra instancia de Controlador raíz inicializada como la propiedad rootViewController de la ventana.
Crea y ejecuta la aplicación. ¡Acabas de implementar la contención de contenedores! Toca en las distintas celdas de la tabla en el menú para reemplazar la diapositiva visible con la nueva que se desliza desde la derecha. Observa cómo, para la instancia del controlador de navegación (llamado "NavC" en el menú), el botón "Izquierda" se ha desplazado un lugar a la derecha y el botón de la barra de menú ha ocupado la posición más a la izquierda. Puedes cambiar la orientación a horizontal y verificar que todo se vea bien.



Conclusión
En este tutorial introductorio, analizamos cómo se implementa la contención del controlador de vista en iOS 6. Desarrollamos una versión simple de una interfaz de aplicación personalizada que ha ganado mucha popularidad y se ve a menudo en aplicaciones muy utilizadas como Facebook y Path. Nuestra implementación fue lo más simple posible, por lo que pudimos analizarlo fácilmente y obtener los conceptos básicos correctos. Hay muchas implementaciones sofisticadas de código abierto de este tipo de controlador que puedes descargar y estudiar. Una búsqueda rápida en Google muestra JASidePAnels y SWRevealViewController, entre otros.
Aquí hay algunas ideas en las que puedes trabajar.
- Haz la implementación más flexible y la API más personalizable.
- Haz que la interfaz sea más bonita. Puedes personalizar la apariencia de la celda de la vista de tabla o dejar que la vista del controlador de vista arroje una sombra en el menú para darle profundidad a la interfaz.
- Haz que la interfaz se adapte mejor a la orientación. Recuerda que los controladores de vista de su hijo recibirán notificaciones de rotación, ¡así que ahí es donde comienza!
- Implementa el reconocimiento de gestos para que puedas arrastrar un controlador de vista hacia la izquierda y hacia la derecha por la pantalla como alternativa a hacer clic en el botón de menú.
- Diseña y desarrolla un flujo de aplicaciones completamente nuevo y novedoso y realiza la interfaz de usuario en código. ¡Lo más probable es que necesites hacer uso de la contención del controlador de vista!
Una cosa que me gustaría mencionar aquí es que en Xcode 4.5 en adelante, hay un nuevo objeto generador de interfaz llamado "Vista de contenedor" que puede mostrar el contenido de un controlador de vista y, por lo tanto, se puede usar para implementar la contención directamente en tu guion gráfico. ¡Feliz codificación!



