Utilizando AutoLayout, ¿cómo guardo una UILabel en el mismo lugar cuando desaparece la barra de navigation?

Tengo un controller de vista con un UILabel que imprime algunas palabras cuando se toca un button. Cuando se toca el button, la barra de navigation se establece en oculta.

Entonces intenté tomar el UILabel y darle estas restricciones en Interface Builder:

introduzca la descripción de la imagen aquí

Pero con eso, cuando presiono el button, el UILabel salta con la barra de navigation desapareciendo, y luego vuelve a upload, corrigiéndose, con un aspecto terrible. Debe permanecer en su lugar de forma permanente, sin importar lo que suceda con la barra de navigation.

Aquí hay un enlace directo a un video corto que muestra lo que sucede.

¿Cómo sería mejor colocarlo para que UILabel permanezca en su lugar?

Proyecto: http://cl.ly/1T2K0V3w1P21

Cuando le indica al controller de navigation que oculte la barra de navigation, cambia el tamaño de su vista de contenido (la vista de ReadingViewController ) a pantalla completa y la vista de contenido presenta sus subvistas para el nuevo tamaño de pantalla completa. De forma pnetworkingeterminada, hace este layout fuera de cualquier bloque de animation, por lo que el nuevo layout entra en vigor instantáneamente.

Para solucionarlo, debe hacer que la vista realice el layout dentro de un bloque de animation. Afortunadamente, el SDK incluye una constante de duración de la animation que oculta la barra de navigation, y la animation usa una curva lineal. Cambie su método hideControls: a esto:

 - (void)hideControls:(BOOL)visible { [UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^{ [self.navigationController setNavigationBarHidden:visible animated:YES]; self.backFiftyWordsButton.hidden = visible; self.forwardFiftyWordsButton.hidden = visible; self.WPMLabel.hidden = visible; self.timeRemainingLabel.hidden = visible; [self.view layoutIfNeeded]; }]; } 

Hay dos cambios aquí. Una es que he envuelto el cuerpo del método en un bloque de animation utilizando la constante UINavigationControllerHideShowBarDuration , por lo que la animation tiene la duración correcta. El otro cambio es que envío el layoutIfNeeded a la vista, dentro del bloque de animation, por lo que las vistas se animarán a sus nuevos frameworks.

Este es el resultado:

animación de barra de navegación

También puede usar este bloque de animation para desvanecer sus tags cambiando sus properties alpha en lugar de sus properties hidden .

ACTUALIZAR

En respuesta a las preguntas en su comentario:

Primero, debe comprender las fases del ciclo de ejecución. Tu aplicación siempre ejecuta un ciclo en su hilo principal. El ciclo, extremadamente simplificado, se ve así:

 while (1) { wait for an event (touch, timer, local or push notification, etc.) Event phase: dispatch the event as appropriate (this often ends up calling into your code, for example calling your tap recognizer's action) Layout phase: send `layoutSubviews` to every view in the on-screen view hierarchy that has been marked as needing layout Draw phase: send `drawRect:` to any view that has been marked as needing display (because it's a new view or it received `setNeedsDisplay` or it has `UIViewContentModeRedraw`) } 

Por ejemplo, si coloca un punto de interrupción en hideControls: toque la pantalla y luego mire el rastreo de stack en el depurador, verá PurpleEventCallback en el rastreo (justo arriba de __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ ). Esto le dice que está en la fase de event handling events. (Purple fue el nombre en key del proyecto de iPhone dentro de Apple).

Si ve CA::Transaction::observer_callback , está en la fase de layout o en la fase de layout. Más arriba en la stack, verá CA::Layer::layout_if_needed o CA::Layer::display_if_needed según la fase en la que se encuentre.

Entonces ese es el ciclo de ejecución y sus fases. Ahora, ¿cuándo se marca una vista que necesita un layout? Se marca como la necesidad de layout cuando recibe setNeedsLayout . Puede enviar esto si, por ejemplo, ha cambiado el contenido que deben mostrar sus vistas y deben moverse o cambiar su tamaño en consecuencia. Pero la vista se enviará automáticamente a setNeedsLayout en dos casos: cuando cambie el tamaño de sus bounds (o el tamaño de su frame ) y cuando subviews su matriz de subviews .

Tenga en count que cambiar el tamaño de la vista o sus sub-vistas no hace que la vista exponga sus subprocesss de inmediato. Simplemente está progtwigdo para diseñar sus subprocesss más adelante, durante la fase de layout del ciclo de ejecución.

Entonces … ¿qué tiene que ver todo esto contigo?

En su método hideControls: hace [self.navigationController setNavigationBarHidden:visible animated:YES] . Supongamos que es visible NO . Esto es lo que hace el controller de navigation en respuesta:

  • Comienza un bloque de animation.
  • Establece la position de la barra de navigation en la parte superior de la pantalla.
  • Aumenta la altura de la vista de contenido en 44 puntos (la altura de la barra de navigation).
  • Disminuye la coorderada Y de la vista de contenido en 44 puntos.
  • Finaliza el bloque de animation.

Los cambios en el marco de la vista de contenido hacen que la vista de contenido se envíe a sí mismo setNeedsLayout .

Tenga en count que los cambios en el marco de la barra de navigation y en el marco de la vista de contenido están animados. Pero los frameworks de las subvistas de la vista de contenido aún no han cambiado. Esos cambios ocurren más tarde, durante la fase de layout.

Por lo tanto, el controller de navigation anima los cambios a su vista de contenido de nivel superior, pero no anima los cambios a las subvistas de su vista de contenido. Tienes que forzar esos cambios para ser animados.

Forzará esos cambios para que se animen dando dos pasos:

  1. Crea un bloque de animation cuyos parameters coinciden con los parameters utilizados por el controller de navigation.
  2. Dentro de ese bloque de animation, obliga a que la fase de layout suceda inmediatamente , enviando layoutIfNeeded a la vista de contenido.

La documentation layoutIfNeeded dice esto:

Use este método para forzar el layout de las subvistas antes del dibujo. Comenzando por el receptor, este método atraviesa hacia arriba a través de la jerarquía de vistas, siempre que las vistas superventas requieran layout. Luego expone todo el tree debajo de ese antepasado.

layoutSubviews todo el tree enviando posts de layoutSubviews a las vistas en el tree, en order de raíz a hoja. Si no está utilizando la function de layout automático, también aplica la máscara de autorrealización de las subvistas de cada vista antes de enviar las vistas de layoutSubviews a la vista.

Por lo tanto, al enviar layoutIfNeeded a su vista de contenido, está forzando el layout automático para actualizar los fotogtwigs de las subvisualizaciones de la vista de contenido inmediatamente, antes de que se vuelva a layoutIfNeeded . Esto significa que esos cambios ocurren dentro de su bloque de animation, por lo que están animados con los mismos parameters (duración y curva) que los cambios en la barra de navigation y la vista de contenido.

Establecer subviews en un bloque de animation es tan importante que Apple definió una opción de animation, UIViewAnimationOptionLayoutSubviews . Si especifica esta opción, al final del bloque de animation, enviará automáticamente layoutIfNeeded . Pero usar esa opción requiere usar la versión larga del post, animateWithDuration:delay:options:animations:completion: por lo que normalmente es más fácil hacer [self.view layoutIfNeeded] tú mismo al final del bloque.

Establezca una restricción del button, el plomo y el rastro y una a una altura fija.

(Copiando mi respuesta de la pregunta que usted envió que está marcada como duplicada: tengo un UILabel posicionado en la pantalla con autolayout, pero cuando oculto la barra de navigation hace que la label "se contracte" por un segundo )

En lugar de la restricción de espacio inferior, puede intentar definir la restricción de espacio superior a la vista de supervisión de la label (que es 22 en la constante), conectarlo como un IBOutlet a su propiedad de vista y animarlo cuando la barra de navigation está oculta o mostrado.

Por ejemplo, declaro la propiedad de espacio superior como topSpaceConstraint:

 @property (weak, nonatomic) IBOutlet NSLayoutConstraint *topSpaceConstraint; 

Luego dentro del método hideControls, puedo animar la restricción:

 - (void)hideControls:(BOOL)visible { if (visible) { [UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^{ self.topSpaceConstraint.constant = 66; //44 is the navigation bar height, you need to find a way not to hardcode this [self.view layoutIfNeeded]; }]; } else { [UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^{ self.topSpaceConstraint.constant = 22; [self.view layoutIfNeeded]; }]; } [self.navigationController setNavigationBarHidden:visible animated:YES]; self.backFiftyWordsButton.hidden = visible; self.forwardFiftyWordsButton.hidden = visible; self.WPMLabel.hidden = visible; self.timeRemainingLabel.hidden = visible; }