uinavigationbar ios
Error en el título de la barra de navegación con interactivePopGestureRecognizer (5)
Estoy teniendo un problema extraño con el título de UINavigationBar
en una aplicación cuando interactivePopGestureRecognizer
juego interactivePopGestureRecognizer
entra en juego. He hecho una aplicación de demostración para mostrar este error.
Preparar:
- El rootViewController es un
UINavigationController
. -
FirstViewController
tiene la barra de navegación oculta, yinteractivePopGestureRecognizer.enabled = NO;
-
Second
y losThirdViewController
s tienen la barra de navegación visible y la función emergente habilitada.
Insecto:
El error se produce al volver de la vista Segunda a la Primera usando la popura. Si tira de la segunda vista hasta la mitad y luego vuelve a la segunda vista, el título de navegación mostrará "Segunda vista" (como se esperaba). Pero cuando vaya a la Tercera vista, el título no cambiará a "Tercera vista". Y luego, al hacer clic en el botón Atrás de la Tercera vista, la barra de navegación se desordenará.
Por favor, echa un vistazo a mi aplicación de demostración. Cualquier ayuda que explique por qué está ocurriendo este error será apreciada. ¡Gracias!
Quitar los arenques rojos
En primer lugar, su ejemplo puede ser enormemente simplificado. Debería eliminar todo el material de viewDidLoad
, ya que es una completa viewDidLoad
falsa y simplemente complica el problema. No debe jugar con el delegado de reconocimiento de gestos pop en cada controlador de cambio de vista; y activar y desactivar el reconocedor de gestos pop es irrelevante para el ejemplo (está activado de forma predeterminada, y solo debe dejarse activado para este ejemplo). Entonces borre este tipo de cosas en los tres controladores de vista:
- (void)viewDidLoad {
[super viewDidLoad];
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
}
(No elimine el código que establece self.title
, aunque podría haber simplificado las cosas al hacerlo en el archivo xib
para cada controlador de vista).
También puede deshacerse de otros métodos no utilizados en todas partes, como los métodos init...
y los métodos de alerta de memoria.
Otro problema, por cierto, es que ha olvidado llamar a super
en sus implementaciones de viewWillAppear:
Se requiere que hagas esto. No creo que eso afecte al error, pero es bueno obedecer todas las reglas antes de comenzar a rastrear estas cosas.
Ahora el error sigue ocurriendo pero tenemos un código mucho más simple, por lo que podemos comenzar a aislar el problema.
Cómo funciona el gesto del pop
Entonces, ¿cuál es la causa del problema? Creo que la forma más obvia de entenderlo es darse cuenta de cómo funciona el gesto pop. Esta es una animación de transición de controlador de vista interactiva. Así es , es una animación . La forma en que funciona es que la animación emergente (diapositiva desde la izquierda) se adjunta a la capa de supervisión, pero con una speed
de 0 para que no se ejecute realmente. A medida que avanza el gesto, el timeOffset
de la capa se actualiza constantemente, de modo que aparece el "marco" correspondiente de la animación. Por lo tanto, parece que estás arrastrando la vista, pero no lo estás haciendo; solo estás haciendo un gesto, y la animación avanza a la misma velocidad y en el mismo grado. He explicado este mecanismo en esta respuesta: https://.com/a/22677298/341994
Lo más importante (preste atención a esta parte), si el gesto se abandona en el medio (lo cual es casi seguro que será), se toma una decisión sobre si el gesto se ha completado a más de la mitad, y en base a esto, ya sea la animación se reproduce rápidamente hasta el final (es decir, la speed
se establece en algo así como 3
) o la animación se ejecuta hacia atrás hasta el inicio (es decir, la speed
se establece en algo así como -3
).
Soluciones y por qué funcionan
Ahora hablemos del error. Hay dos complicaciones aquí que accidentalmente has golpeado:
A medida que comienzan la animación pop y el gesto pop, se llama a
viewWillAppear:
para el controlador de vista anterior, aunque la vista puede no aparecer finalmente (porque es un gesto interactivo y el gesto puede cancelarse). Esto puede ser un problema grave si está acostumbrado a suponer queviewWillAppear:
siempre es seguido por la vista que realmente toma el control de la pantalla (yviewDidAppear:
ser llamado), porque esta es una situación en la que esas cosas podrían no ocurrir. (Como dice Apple en los videos de la WWDC 2013, "la vista aparecerá" en realidad significa que "la vista podría aparecer".)Hay un conjunto secundario de animaciones, a saber, todo lo relacionado con la barra de navegación: el cambio de título (se supone que se desvanece) y, en este caso, el cambio entre no oculto y oculto. El tiempo de ejecución está intentando coordinar el conjunto secundario de animaciones con la animación de vista deslizante. Pero lo has hecho difícil al no pedir animación cuando la barra está oculta o mostrada.
Por lo tanto, como ya le han dicho, una solución es cambiar animated:NO
a animated:YES
largo de su código. De esta manera, la visualización y el ocultamiento de la barra de navegación se ordenan como parte de la animación . Por lo tanto, cuando el gesto se cancela y la animación se ejecuta hacia atrás hasta el inicio, la visualización / ocultación de la navegación también se realiza hacia atrás hasta el inicio: las dos cosas ahora se mantienen coordinadas.
Pero ¿y si realmente no quieres hacer ese cambio? Bueno, otra solución es cambiar viewWillAppear:
to viewDidAppear:
todas partes. Como ya he dicho, viewWillAppear:
se llama al comienzo de la animación, incluso si el gesto no se completa, lo que está causando que las cosas salgan de control. Pero viewDidAppear:
se llama solo si el gesto se completa (no se cancela) y cuando la animación ya ha terminado.
¿Cuál de esas dos soluciones prefiero? ¡Ninguno de ellos! Ambos te obligan a hacer cambios que no quieres hacer. La solución real , me parece, es utilizar el coordinador de transición .
El Coordinador de Transición
El coordinador de transición es un objeto suministrado por el sistema para este propósito , es decir, para detectar que estamos involucrados en una transición interactiva y comportarnos de manera diferente dependiendo de si se cancela o no.
Concéntrese solo en la implementación viewWillAppear:
de viewWillAppear:
Aquí es donde las cosas se complican. Cuando estás en TwoViewController e inicias el gesto de paneo desde la izquierda, se visualiza el viewWillAppear:
de viewWillAppear:
Pero luego cancelas, soltando el gesto sin completarlo. Solo en ese caso, no quieres hacer lo que estabas haciendo en la vista de viewWillAppear:
. Y eso es exactamente lo que el coordinador de transición le permite hacer .
Aquí, entonces, es una reescritura de la vista de control de control de viewWillAppear:
. Esto soluciona el problema sin tener que realizar ningún otro cambio:
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
id<UIViewControllerTransitionCoordinator> tc = self.transitionCoordinator;
if (tc && [tc initiallyInteractive]) {
[tc notifyWhenInteractionEndsUsingBlock:
^(id<UIViewControllerTransitionCoordinatorContext> context) {
if ([context isCancelled]) {
// do nothing!
} else { // not cancelled, do it
[self.navigationController setNavigationBarHidden:YES animated:NO];
}
}];
} else { // not interactive, do it
[self.navigationController setNavigationBarHidden:YES animated:NO];
}
}
Esto es lo que lo arregló para mí (Swift)
1er controlador de vista:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
Controles de 2da y 3ra vista:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: animated)
}
La solución es simple, pero en este momento no tengo ninguna explicación de por qué sucede esto.
Una vez que su OneViewController cambie su viewWillAppear
, viewWillAppear
en
-(void)viewWillAppear:(BOOL)animated{
// [self.navigationController setNavigationBarHidden:YES animated:NO];
self.navigationController.navigationBar.hidden = YES;
}
y en la segunda y tercera vista los controladores cambian a,
-(void)viewWillAppear:(BOOL)animated{
//[self.navigationController setNavigationBarHidden:NO animated:NO];
self.navigationController.navigationBar.hidden = NO;
}
Extraño, pero esto solucionará el problema cuando usamos directamente la propiedad oculta de UINavigationBar.
No sé cómo hacer que "FirstViewController tenga la barra de navegación oculta".
Tengo el mismo problema, y lo arreglé reemplazando
self.navigationController.navigationBarHidden = YES / NO;
por
[self.navigationController setNavigationBarHidden:YES / NO animated:animated];
Renuncié a intentar hacer que este trabajo utilizara mi propio reconocedor de deslizamiento que abre la pila de navegación:
override func viewDidLoad() {
super.viewDidLoad()
// disable system swipe back gesture and add our own
navigationController?.interactivePopGestureRecognizer?.enabled = false
let swipeBackGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "swipeBackAction:")
swipeBackGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Right
tableView.addGestureRecognizer(swipeBackGestureRecognizer)
}
func swipeBackAction(sender: UISwipeGestureRecognizer) {
navigationController?.popViewControllerAnimated(true)
}
- Deshabilita el sistema interactivePopGestureRecognizer
- Crea tu propio UISwipeGestureRecognizer con una dirección correcta
- Abre la pila de navegación animada cuando se detecta el golpe.