presentViewController: animated: YES view no aparecerá hasta que el usuario vuelva a tocar

presentViewController:animated:completion comportamiento extraño con presentViewController:animated:completion . Lo que estoy haciendo es esencialmente un juego de adivinanzas.

Tengo un UIViewController (frequencyViewController) que contiene una UITableView (frequencyTableView). Cuando el usuario toca en la fila de questionTableView que contiene la respuesta correcta, se debe instanciar una vista (correctViewController) y su vista debe deslizarse hacia arriba desde la parte inferior de la pantalla, como una vista modal. Esto le dice al usuario que tiene una respuesta correcta y restablece la frecuenciaViewController detrás de ella lista para la siguiente pregunta. correctViewController se descarta al presionar un botón para revelar la siguiente pregunta.

Todo esto funciona correctamente todas las veces, y la vista correcta de ViewController aparece instantáneamente mientras presentViewController:animated:completion haya sido animated:NO .

Si configuro animated:YES , correctViewController se inicializa y realiza llamadas a viewDidLoad . Sin embargo, viewWillAppear , viewDidAppear y el bloque de finalización de presentViewController:animated:completion no se presentViewController:animated:completion . La aplicación simplemente se queda ahí y muestra frequencyViewController hasta que hago un segundo toque. Ahora, se muestran viewWillAppear, viewDidAppear y el bloque de finalización.

Investigué un poco más, y no es solo otro toque que hará que continúe. Parece que si inclino o sacudo mi iPhone, esto también puede provocar que active viewWillLoad, etc. Es como si estuviera esperando cualquier otra entrada del usuario antes de que avance. Esto sucede en un iPhone real y en el simulador, que probé enviando el comando de sacudida al simulador.

Realmente no sé qué hacer con esto … agradecería cualquier ayuda que alguien pueda brindar.

Gracias

Aquí está mi código. Es bastante simple …

Este es el código en questionViewController que actúa como el delegado a la questionTableView

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.row != [self.frequencyModel currentFrequencyIndex]) { // If guess was wrong, then mark the selection as incorrect NSLog(@"Incorrect Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]); UITableViewCell *cell = [self.frequencyTableView cellForRowAtIndexPath:indexPath]; [cell setBackgroundColor:[UIColor colorWithRed:240/255.0f green:110/255.0f blue:103/255.0f alpha:1.0f]]; } else { // If guess was correct, show correct view NSLog(@"Correct Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]); self.correctViewController = [[HFBCorrectViewController alloc] init]; self.correctViewController.delegate = self; [self presentViewController:self.correctViewController animated:YES completion:^(void){ NSLog(@"Completed Presenting correctViewController"); [self setUpViewForNextQuestion]; }]; } } 

Este es el conjunto de ViewController correcto

 @implementation HFBCorrectViewController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization NSLog(@"[HFBCorrectViewController initWithNibName:bundle:]"); } return self; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. NSLog(@"[HFBCorrectViewController viewDidLoad]"); } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@"[HFBCorrectViewController viewDidAppear]"); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (IBAction)close:(id)sender { NSLog(@"[HFBCorrectViewController close:sender:]"); [self.delegate didDismissCorrectViewController]; } @end 

Editar:

Encontré esta pregunta antes: UITableView y presentViewController tardan 2 clics en mostrarse

Y si cambio mi código didSelectRow a este, funciona muy bien con la animación … Pero es complicado y no tiene sentido en cuanto a por qué no funciona en primer lugar. Entonces no cuento eso como una respuesta …

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.row != [self.frequencyModel currentFrequencyIndex]) { // If guess was wrong, then mark the selection as incorrect NSLog(@"Incorrect Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]); UITableViewCell *cell = [self.frequencyTableView cellForRowAtIndexPath:indexPath]; [cell setBackgroundColor:[UIColor colorWithRed:240/255.0f green:110/255.0f blue:103/255.0f alpha:1.0f]]; // [cell setAccessoryType:(UITableViewCellAccessoryType)] } else { // If guess was correct, show correct view NSLog(@"Correct Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]); //////////////////////////// // BELOW HERE ARE THE CHANGES [self performSelector:@selector(showCorrectViewController:) withObject:nil afterDelay:0]; } } -(void)showCorrectViewController:(id)sender { self.correctViewController = [[HFBCorrectViewController alloc] init]; self.correctViewController.delegate = self; self.correctViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; [self presentViewController:self.correctViewController animated:YES completion:^(void){ NSLog(@"Completed Presenting correctViewController"); [self setUpViewForNextQuestion]; }]; } 

He encontrado el mismo problema hoy. Profundicé en el tema y parece que está relacionado con el runloop principal durmiendo.

En realidad, es un error muy sutil, porque si tienes la menor animación de comentarios, temporizadores, etc. en tu código, este problema no saldrá a la superficie debido a que estas fonts mantendrán vivo el runloop. Encontré el problema al usar una UITableViewCell que tenía su selectionStyle configurado en UITableViewCellSelectionStyleNone , de modo que ninguna animación de selección desencadenó el runloop después de que se ejecutara el manejador de selección de filas.

Para solucionarlo (hasta que Apple haga algo), puedes disparar el runloop principal de varias maneras:

La solución menos intrusiva es llamar a CFRunLoopWakeUp :

 [self presentViewController:vc animated:YES completion:nil]; CFRunLoopWakeUp(CFRunLoopGetCurrent()); 

O puede poner un bloque vacío en la cola principal:

 [self presentViewController:vc animated:YES completion:nil]; dispatch_async(dispatch_get_main_queue(), ^{}); 

Es gracioso, pero si agitas el dispositivo, también activará el ciclo principal (tiene que procesar los eventos de movimiento). Lo mismo con los grifos, pero eso está incluido en la pregunta original 🙂 Además, si el sistema actualiza la barra de estado (por ejemplo, las actualizaciones del reloj, la intensidad de la señal WiFi cambia etc.) que también activará el ciclo principal y presentará la vista controlador.

Para cualquier persona interesada, escribí un proyecto de demostración mínimo del problema para verificar la hipótesis de runloop: https://github.com/tzahola/present-bug

También reporté el error a Apple.

Mira esto: https://devforums.apple.com/thread/201431 Si no quieres leerlo todo, la solución para algunas personas (incluyéndome a mí) era hacer que el presente presentViewController llame explícitamente al hilo principal:

 dispatch_async(dispatch_get_main_queue(), ^{ [self presentViewController:myVC animated:YES completion:nil]; }); 

Probablemente iOS7 esté estropeando los hilos en didSelectRowAtIndexPath .

Lo evité en Swift 3.0 usando el siguiente código:

 DispatchQueue.main.async { self.present(UIViewController(), animated: true, completion: nil) } 

Llamar a [viewController view] en el controlador de vista que se presentó fue el truco para mí.

Me gustaría saber qué [self setUpViewForNextQuestion]; hace.

Puedes intentar llamar a [self.correctViewController.view setNeedsDisplay]; al final de su bloque de finalización en presentViewController .

He escrito una extensión (categoría) con método Swizzling para UIViewController que resuelve el problema. Gracias a AX y NSHipster por las sugerencias de implementación ( swift / objective-c ).

Rápido

 extension UIViewController { override public class func initialize() { struct DispatchToken { static var token: dispatch_once_t = 0 } if self != UIViewController.self { return } dispatch_once(&DispatchToken.token) { let originalSelector = Selector("presentViewController:animated:completion:") let swizzledSelector = Selector("wrappedPresentViewController:animated:completion:") let originalMethod = class_getInstanceMethod(self, originalSelector) let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)) if didAddMethod { class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) } else { method_exchangeImplementations(originalMethod, swizzledMethod) } } } func wrappedPresentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) { dispatch_async(dispatch_get_main_queue()) { self.wrappedPresentViewController(viewControllerToPresent, animated: flag, completion: completion) } } } 

C objective

 #import  @implementation UIViewController (Swizzling) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(presentViewController:animated:completion:); SEL swizzledSelector = @selector(wrappedPresentViewController:animated:completion:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } - (void)wrappedPresentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^ __nullable)(void))completion { dispatch_async(dispatch_get_main_queue(),^{ [self wrappedPresentViewController:viewControllerToPresent animated:flag completion:completion]; }); } @end 

Compruebe si su celda en el guión gráfico tiene Selection = none

Si es así, cámbielo a azul o gris y debería funcionar