ios - unprepared - swift constraints programmatically
¿Cómo utilizar Auto Layout con transiciones de contenedor? (4)
¿Cómo se puede usar Diseño automático con el método de transición de contenedor UIViewController?
-(void)transitionFromViewController:(UIViewController *)fromViewController
toViewController:(UIViewController *)toViewController
duration:(NSTimeInterval)duration
options:(UIViewAnimationOptions)options
animations:(void (^)(void))animations
completion:(void (^)(BOOL finished))completion;
Tradicionalmente, al utilizar Springs / Struts, estableces los fotogramas iniciales (justo antes de llamar a este método) y configuras los fotogramas finales en el bloque de animación que pasas al método.
Ese método hace el trabajo de agregar la vista a la jerarquía de vista y ejecutar las animaciones por usted.
El problema es que no podemos agregar restricciones iniciales en el mismo lugar (antes de la llamada al método) porque la vista aún no se ha agregado a la jerarquía de vistas.
¿Alguna idea de cómo puedo usar este método junto con Diseño automático?
A continuación se muestra un ejemplo (gracias cocoanetics) de hacer esto utilizando Springs / Struts (frames) http://www.cocoanetics.com/2012/04/containing-viewcontrollers
- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{
// XXX We can''t add constraints here because the view is not yet in the view hierarchy
// animation setup
toViewController.view.frame = _containerView.bounds;
toViewController.view.autoresizingMask = _containerView.autoresizingMask;
// notify
[fromViewController willMoveToParentViewController:nil];
[self addChildViewController:toViewController];
// transition
[self transitionFromViewController:fromViewController
toViewController:toViewController
duration:1.0
options:UIViewAnimationOptionTransitionCurlDown
animations:^{
}
completion:^(BOOL finished) {
[toViewController didMoveToParentViewController:self];
[fromViewController removeFromParentViewController];
}];
}
Empezando a pensar en el método de utilidad transitionFromViewController: toViewController: duration: options: animations: no se puede hacer que la finalización funcione limpiamente con Auto Layout.
Por ahora, he reemplazado mi uso de este método con llamadas a cada uno de los métodos de contención de "nivel inferior" directamente. Es un poco más código, pero parece dar un mayor control.
Se parece a esto:
- (void) performTransitionFromViewController:(UIViewController*)fromVc toViewController:(UIViewController*)toVc {
[fromVc willMoveToParentViewController:nil];
[self addChildViewController:toVc];
UIView *toView = toVc.view;
UIView *fromView = fromVc.view;
[self.containerView addSubview:toView];
// TODO: set initial layout constraints here
[self.containerView layoutIfNeeded];
[UIView animateWithDuration:.25
delay:0
options:0
animations:^{
// TODO: set final layout constraints here
[self.containerView layoutIfNeeded];
} completion:^(BOOL finished) {
[toVc didMoveToParentViewController:self];
[fromView removeFromSuperview];
[fromVc removeFromParentViewController];
}];
}
Espero que tu pregunta gane algo de tracción porque creo que es buena. No tengo una respuesta definitiva para ti, pero puedo describir mis propias experiencias con situaciones similares a las tuyas.
Esta es la conclusión que he extraído de mis experiencias: no puede usar el diseño automático directamente en la vista raíz de un controlador de vista. Tan pronto como establezco translatesAutoresizingMaskIntoConstraints
a NO
en una vista raíz, empiezo a recibir errores, o algo peor.
Entonces uso una solución híbrida. Establezco marcos y uso la conversión automática para posicionar y dimensionar la vista raíz en un diseño que de otra forma se configura mediante el diseño automático. Por ejemplo, así es como cargo un controlador de vista de página como controlador de vista infantil en viewDidLoad
en una aplicación que usa diseño automático:
self.pageViewController = ...
...
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
// could not get constraints to work here (using autoresizing mask)
self.pageViewController.view.frame = self.view.bounds;
[self.pageViewController didMoveToParentViewController:self];
Esta es la forma en que Apple carga un controlador de vista infantil en la plantilla "Aplicación basada en páginas" de Xcode, y esto se realiza en un proyecto habilitado para diseño automático.
Entonces, si yo fuera tú, intentaría establecer marcos para animar la transición del controlador de vista y ver qué sucede. Déjame saber cómo funciona.
Hay dos soluciones dependiendo de si simplemente necesita posicionar la vista mediante el diseño automático (fácil) frente a la necesidad de animar los cambios de restricciones de diseño automático (más difícil).
TL; versión DR
Si solo necesita posicionar una vista mediante diseño automático, puede usar el -[UIViewController transitionFromViewController:toViewController:duration:options:animations:completion:]
e instalar las restricciones en el bloque de animación.
Si necesita animar los cambios de restricción de diseño automático, debe usar una +[UIView animateWithDuration:delay:options:animations:completion:]
genérica +[UIView animateWithDuration:delay:options:animations:completion:]
y agregar el controlador secundario regularmente.
Solución 1: posicione una vista a través del diseño automático
Abordemos primero el primer caso fácil. En este escenario, la vista debe posicionarse mediante el diseño automático, de modo que los cambios a la altura de la barra de estado (p. Ej. Al elegir Alternar barra de estado durante la llamada ), entre otras cosas, no alejen cosas de la pantalla.
Como referencia, aquí está el código oficial de Apple con respecto a la transición de un controlador de vista a otro:
- (void) cycleFromViewController: (UIViewController*) oldC
toViewController: (UIViewController*) newC
{
[oldC willMoveToParentViewController:nil]; // 1
[self addChildViewController:newC];
newC.view.frame = [self newViewStartFrame]; // 2
CGRect endFrame = [self oldViewEndFrame];
[self transitionFromViewController: oldC toViewController: newC // 3
duration: 0.25 options:0
animations:^{
newC.view.frame = oldC.view.frame; // 4
oldC.view.frame = endFrame;
}
completion:^(BOOL finished) {
[oldC removeFromParentViewController]; // 5
[newC didMoveToParentViewController:self];
}];
}
En lugar de usar marcos como en el ejemplo anterior, debemos agregar restricciones. La pregunta es dónde agregarlos. No podemos agregarlos en el marcador (2) anterior, ya que newC.view
no está instalado en la jerarquía de vistas. Solo se instala en el momento en que llamamos transitionFromViewController...
(3). Eso significa que podemos instalar las restricciones justo después de la llamada a transitionFromViewController, o podemos hacerlo como la primera línea en el bloque de animación. Ambos deberían funcionar. Si quieres hacerlo a la mayor brevedad, colocarlo en el bloque de animación es el camino a seguir. Más abajo se discutirá más sobre el orden de cómo se llaman estos bloques.
En resumen, para solo posicionar a través del diseño automático, use una plantilla como:
- (void)cycleFromViewController:(UIViewController *)oldViewController
toViewController:(UIViewController *)newViewController
{
[oldViewController willMoveToParentViewController:nil];
[self addChildViewController:newViewController];
newViewController.view.alpha = 0;
[self transitionFromViewController:oldViewController
toViewController:newViewController
duration:0.25
options:0
animations:^{
newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
// create constraints for newViewController.view here
newViewController.view.alpha = 1;
}
completion:^(BOOL finished) {
[oldViewController removeFromParentViewController];
[newViewController didMoveToParentViewController:self];
}];
// or create constraints right here
}
Solución 2: Animación de cambios de restricciones
Animar cambios de restricciones no es tan simple, porque no recibimos una devolución de llamada entre cuando la vista se adjunta a la jerarquía y cuando se llama al bloque de animación a través del método transitionFromViewController...
Como referencia, esta es la forma estándar de agregar / eliminar un controlador de vista hijo:
- (void) displayContentController: (UIViewController*) content;
{
[self addChildViewController:content]; // 1
content.view.frame = [self frameForContentController]; // 2
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self]; // 3
}
- (void) hideContentController: (UIViewController*) content
{
[content willMoveToParentViewController:nil]; // 1
[content.view removeFromSuperview]; // 2
[content removeFromParentViewController]; // 3
}
Al comparar estos dos métodos y el ciclo original FromViewController: publicado anteriormente, vemos que transitionFromViewController se encarga de dos cosas para nosotros:
-
[self.view addSubview:self.currentClientView];
-
[content.view removeFromSuperview];
Al agregar algunos registros (omitidos en esta publicación), podemos tener una buena idea de cuándo se llaman estos métodos.
Después de hacerlo, parece que el método se implementa de una manera similar a la siguiente:
- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
{
[self.view addSubview:toViewController.view]; // A
animations(); // B
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[fromViewController.view removeFromSuperview];
completion(YES);
});
}
Ahora es claro ver por qué no es posible usar transitionFromViewController para animar los cambios de restricción. La primera vez que puede inicializar restricciones es después de que se agrega la vista (línea A). Las restricciones deben estar animadas en el bloque de animations()
(línea B), pero no hay forma de ejecutar el código entre estas dos líneas.
Por lo tanto, debemos usar un bloque de animación manual, junto con el método estándar de animación de cambios de restricción :
- (void)cycleFromViewController:(UIViewController *)oldViewController
toViewController:(UIViewController *)newViewController
{
[oldViewController willMoveToParentViewController:nil];
[self addChildViewController:newViewController];
[self.view addSubview:newViewController.view];
newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
// TODO: create initial constraints for newViewController.view here
[newViewController.view layoutIfNeeded];
// TODO: update constraint constants here
[UIView animateWithDuration:0.25
animations:^{
[newViewController.view layoutIfNeeded];
}
completion:^(BOOL finished) {
[oldViewController.view removeFromSuperview];
[oldViewController removeFromParentViewController];
[newViewController didMoveToParentViewController:self];
}];
}
Advertencias
Esto no es equivalente a cómo el guión gráfico incorpora un controlador de vista de contenedor. Por ejemplo, si compara el valor de tralationsAutoresizingMaskIntoConstraints de la vista incrustada a través de un guión gráfico vs. el método anterior, informará YES
para el guión gráfico y NO
(obviamente, ya que explícitamente lo configuramos en NO) para el método que recomiendo arriba.
Esto puede generar inconsistencias en su aplicación, ya que ciertas partes del sistema parecen depender de la contención de UIViewController para ser utilizado con tralatesAutoresizingMaskIntoConstraints establecido en NO
. Por ejemplo, en un iPad Air (8.4), puede obtener un comportamiento extraño al girar de vertical a horizontal .
La solución simple parece ser mantener newViewController.view.frame = newViewController.view.superview.bounds
establecido en NO
, luego establecer newViewController.view.frame = newViewController.view.superview.bounds
. Sin embargo, a menos que tenga mucho cuidado cuando se llama este método, lo más probable es que le dé un diseño visual incorrecto. (Nota: la manera en que el guión gráfico asegura el tamaño de la vista correctamente es estableciendo la propiedad de autoresize
la vista incrustada en W+H
Imprimir el marco justo después de agregar la subvista también revelará una diferencia entre el enfoque del guión gráfico frente al enfoque programático, lo que sugiere que Apple está configurando el marco directamente en la vista contenida).
La solución real parece ser establecer sus restricciones en el bloque de animación de transitionFromViewController:toViewController:duration:options:animations:
[self transitionFromViewController:fromViewController
toViewController:toViewController
duration:1.0
options:UIViewAnimationOptionTransitionCurlDown
animations:^{
// SET UP CONSTRAINTS HERE
}
completion:^(BOOL finished) {
[toViewController didMoveToParentViewController:self];
[fromViewController removeFromParentViewController];
}];