Al presionar el controller de vista dentro de viewDidAppear no funciona

Pasos para reproducir

1) Cree un controller de navigation y 3 controlleres de vista.

firstViewController.m:

- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@"DEBUG: first screen did appear"); [self.navigationController pushViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"secondScreen"] animated:NO]; } 

secondViewController.m:

 - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@"DEBUG: second screen did appear"); [self.navigationController pushViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"thirdScreen"] animated:YES]; } 

thirdViewController.m:

 - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@"DEBUG: third screen did appear"); } 

2) make firstViewController (también conocido como firstScreen en storyboard) el controller de vista raíz del controller de navigation.

3) Ejecutar la aplicación y observe que la barra de navigation se ha actualizado para mostrar el título de la tercera pantalla, pero que aún muestra el contenido de la segunda pantalla.

Notas

He intentado usar UINavigationControllerDelegate -( void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated método -( void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated , ya que parece disparar después del método viewDidAppear , pero no solucionó el problema problema.

También intenté configurar manualmente los viewControllers del controller de viewControllers pensando que viewControllers alguna lógica de "este controller de vista está activa" y permitiría que el empuje problemático funcione, pero no funcionó.

Solución

La única solución que se me ocurrió fue usar una llamada retardada para presionar el controller de vista deseado en secondViewController.m :

 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 250 * USEC_PER_SEC), dispatch_get_main_queue(), ^{ [self.navigationController pushViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"thirdScreen"] animated:YES]; }); 

Problema

Me gustaría entender por qué esto no funciona como se esperaba. Sobre la base de algunas otras respuestas de SO que he visto en preguntas semi relevantes, puede tener algo que ver con un ciclo de ejecución, pero no puedo confirmarlo o negarlo (parece posible ya que enviar el impulso le permite funcionar).

¿Alguien más con más conocimiento / experiencia me puede iluminar?

¡Gracias!

Esta es una pregunta interesante. Estoy bastante seguro de que si estabas animated:YES al presionar el segundo controller de vista en firstViewController.m , el estado final de la interfaz de usuario sería el esperado, con el contenido y el título de la tercera pantalla bien visibles.

Sin embargo, claramente este no es el efecto de transición que estás buscando. ¿Y por qué la bandera animated tiene un ápice de diferencia de todos modos?

Si establece un punto de interrupción en -viewDidAppear: y observe los rastros de stack tanto para el caso donde está animated == YES y animated == NO , me parece que cuando está animated == NO , -viewDidAppear: se invoca durante una vista operación de layout en UINavigationController . Mi dinero es que esta es la razón por la que la vista final se ve incorrecta; realizar un push ahora lo haría antes de que el push anterior haya terminado por completo.

Aquí es donde entran en juego las consideraciones del ciclo de ejecución. Queremos que el layout de vista del UINavigationController (que está sucediendo en el ciclo de ciclo actual del ciclo de ejecución principal) termine antes de pedir el siguiente impulso. Una manera simple de lograrlo es hacer queue para que suceda en el siguiente ciclo del bucle de ejecución principal. Un retraso sin duda hará el truco (creo que un retraso de 0 es suficiente para retrasar el siguiente ciclo de ciclo de ejecución, por lo que podría intentar replace 250 * USEC_PER_SEC con 0 ). Otra forma sería enviar la acción a la queue principal:

 dispatch_async(dispatch_get_main_queue(), ^{ [self.navigationController pushViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"thirdScreen"] animated:YES]; }); 

Entonces mi respuesta es especulativa, pero basada en alguna evidencia. Se siente ligeramente insatisfactorio que, al realizar transiciones UINavigationController , -viewDidAppear: solo indica el verdadero final de la transición cuando está animada, sin embargo, ese parece ser el caso.

No estoy seguro de por qué esto no funciona exactamente, probablemente tiene que ver con el time. Pero para empujar los controlleres de múltiples vistas a un controller de navigation, el método preferido es usar setViewControllers:animated:

 NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[navigationController viewControllers]]; [viewControllers addObjectsFromArray:viewControllersToPush]; [navigationController setViewControllers:viewControllers animated:YES];