presentViewController: animato: la vista SÌ non apparirà finché l'utente non tocca di nuovo


93

Sto ottenendo uno strano comportamento con presentViewController:animated:completion. Quello che sto facendo è essenzialmente un gioco d'ipotesi.

Ho un UIViewController(frequencyViewController) contenente un UITableView(frequencyTableView). Quando l'utente tocca la riga in questionTableView contenente la risposta corretta, dovrebbe essere istanziata una vista (correctViewController) e la sua vista dovrebbe scorrere verso l'alto dalla parte inferiore dello schermo, come una vista modale. Questo dice all'utente che ha una risposta corretta e reimposta il frequencyViewController dietro di esso pronto per la domanda successiva. correctViewController viene chiuso alla pressione di un pulsante per rivelare la domanda successiva.

Tutto questo funziona correttamente ogni volta e la vista del correctViewController appare istantaneamente finché presentViewController:animated:completionha animated:NO.

Se imposto animated:YES, correctViewController viene inizializzato ed effettua chiamate a viewDidLoad. Tuttavia viewWillAppear, viewDidAppeare il blocco di completamento da presentViewController:animated:completionnon vengono chiamati. L'app rimane lì, mostrando ancora frequencyViewController fino a quando non faccio un secondo tocco. Ora vengono chiamati viewWillAppear, viewDidAppear e il blocco di completamento.

Ho indagato un po 'di più e non è solo un altro tocco che lo farà continuare. Sembra che se inclino o scuoto il mio iPhone questo può anche causare l'attivazione del viewWillLoad ecc. È come se aspettasse qualsiasi altro bit di input dell'utente prima che proceda. Questo accade su un vero iPhone e nel simulatore, cosa che ho dimostrato inviando il comando shake al simulatore.

Sono davvero perplesso su cosa fare al riguardo ... Apprezzerei davvero qualsiasi aiuto che qualcuno possa fornire.

Grazie

Ecco il mio codice. È abbastanza semplice ...

Questo è il codice in questionViewController che funge da delegato per 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];
        }];
    }
}

Questo è tutto il correttoViewController

@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

Modificare:

Ho trovato questa domanda in precedenza: UITableView e presentViewController richiedono 2 clic per essere visualizzati

E se cambio il mio didSelectRowcodice in questo, funziona molto tempo con l'animazione ... Ma è disordinato e non ha senso sul motivo per cui non funziona in primo luogo. Quindi non lo considero una risposta ...

- (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];
    }];
}

1
Ho lo stesso problema. Ho verificato con NSLogs che non chiamo metodi che impiegano molto tempo per essere eseguiti. Sembra che sia solo l'animazione che presentViewController:dovrebbe innescarsi inizia con un grande ritardo. Questo sembra essere un bug in iOS 7 ed è anche discusso nei forum di Apple Dev.
Theo

Ho lo stesso problema. Sembra un bug di iOS7 - non si verifica su iOS6. Inoltre, nel mio caso accade solo la prima volta dopo aver aperto il controller della vista di presentazione. @ Theo, puoi fornire un collegamento ai forum di sviluppo di Apple?
AX

Risposte:


158

Oggi ho riscontrato lo stesso problema. Ho approfondito l'argomento e sembra che sia correlato al runloop principale addormentato.

In realtà è un bug molto sottile, perché se hai la minima animazione di feedback, timer, ecc. Nel tuo codice, questo problema non emergerà perché il runloop sarà mantenuto in vita da queste fonti. Ho trovato il problema utilizzando un UITableViewCellche aveva selectionStyleimpostato su UITableViewCellSelectionStyleNone, in modo che nessuna animazione di selezione abbia attivato il ciclo di esecuzione dopo che è stato eseguito il gestore di selezione delle righe.

Per risolverlo (finché Apple non fa qualcosa) puoi attivare il runloop principale in diversi modi:

La soluzione meno invadente è chiamare CFRunLoopWakeUp:

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

Oppure puoi accodare un blocco vuoto alla coda principale:

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

È divertente, ma se agiti il ​​dispositivo, attiverà anche il loop principale (deve elaborare gli eventi di movimento). Stessa cosa con i tocchi, ma è incluso nella domanda originale :) Inoltre, se il sistema aggiorna la barra di stato (ad esempio, l'orologio si aggiorna, la potenza del segnale WiFi cambia ecc.) Questo attiverà anche il loop principale e presenterà la vista controller.

Per chiunque fosse interessato ho scritto un progetto dimostrativo minimo del problema per verificare l'ipotesi di runloop: https://github.com/tzahola/present-bug

Ho anche segnalato il bug ad Apple.


Grazie Tamás. Sono ottime informazioni. Spiega perché a volte dovevo toccare per dare il via al ciclo di esecuzione, o talvolta semplicemente aspettare e avrebbe funzionato. L'ho contrassegnata come la soluzione in quanto non sembra che ci sia nient'altro che possiamo fare fino a quando il bug non viene corretto.
HalfNormalled

La mela scava una grande fossa, ci cade dentro e mi sento gravemente ferita.
OpenThread

7
OMG, questo è così divertente! BTW, questo bug esiste ancora.
igrrik

1
Ho avuto lo stesso identico problema era davvero fastidioso, per fortuna questo lo ha risolto.
Mark

1
Confermato che questo problema si verifica ancora in iOS 13
Eric Horacek

69

Controlla questo: https://devforums.apple.com/thread/201431 Se non vuoi leggerlo tutto, la soluzione per alcune persone (me compreso) era di effettuare la presentViewControllerchiamata esplicitamente sul thread principale:

Swift 4.2:

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

Obiettivo-C:

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

Probabilmente iOS7 sta rovinando i thread didSelectRowAtIndexPath.


wow - questo ha funzionato per me. Anche io vedevo i ritardi. Non riesco a capire perché questo dovrebbe essere necessario. Grazie per aver postato questo.
drudru

4
Archiviato un radar con Apple su questo problema: rdar: // 19563577 openradar.appspot.com/19563577
mluisbrown

1
Sono su iOS 8 e questo non lo risolve per me: segnala sempre di essere sul thread principale, ma vedo ancora il problema descritto.
Robert

7

L'ho bypassato in Swift 3.0 utilizzando il codice seguente:

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

1
risolve il problema. Ho avuto lo stesso problema perché stavo chiamando presentin una richiamata di chiusura.
Jeremy Piednoel

2

Chiamare [viewController view]il controller di visualizzazione presentato ha funzionato per me.


Questo ha funzionato per me su iOS 8. Penso che sia meglio di dispatch_async.
Robert

0

Sarei curioso di vedere cosa [self setUpViewForNextQuestion]; fa.

Potresti provare a chiamare [self.correctViewController.view setNeedsDisplay];alla fine del blocco di completamento presentViewController.


In questo momento, setUpViewForNextQuestion chiama semplicemente reloadData su tableview (per cambiare tutti i colori di sfondo in bianco se qualcuno fosse stato impostato su rosso da un'ipotesi errata). Ma eventuali modifiche potrebbero fare la differenza? Il problema è che correctViewController non appare fino a quando l'uso non tocca di nuovo, quindi questo blocco di completamento non viene chiamato fino a dopo quel secondo tocco.
HalfNormalled

Ho provato il tuo suggerimento, ma non ha fatto alcuna differenza. Come ho detto, la vista non viene visualizzata, quindi il blocco di completamento non viene chiamato fino a dopo il secondo tocco o il gesto di scuotimento.
HalfNormalled

hmm. per i principianti, utilizzerei i punti di interruzione per confermare che il codice della presentazione non viene chiamato fino al secondo tocco. (rispetto a essere chiamato al primo tocco, ma non essere disegnato sullo schermo fino a un momento successivo). se quest'ultimo è vero, prova a forzare la presentazione sul thread principale. Quando si utilizza "performSelectorWithDelay: 0", l'applicazione forza l'attesa fino alla successiva iterazione del ciclo di esecuzione prima dell'esecuzione. Potrebbe essere correlato al threading.
Nick

Grazie, Nick. Come verifico l'utilizzo dei punti di interruzione? Al momento sto usando NSLog per controllare quando viene chiamato ogni metodo. Questo è quello che ho ora: Tap 1 Correct Guess: 1 kHz 18:04:57.173 Frequency[641:60b] [HFBCorrectViewController initWithNibName:bundle:] 18:04:57.177 Frequency[641:60b] [HFBCorrectViewController viewDidLoad] Ora non succede nulla fino a: Tap 218:05:00.515 Frequency[641:60b] [HFBCorrectViewController viewDidAppear] 18:05:00.516 Frequency[641:60b] Completed Presenting correctViewController 18:05:00.517 Frequency[641:60b] [HFBFrequencyViewController setUpViewForNextQuestion]
HalfNormalled

Spiacente, avrebbe dovuto essere più esplicito. Capisco come utilizzare i punti di interruzione. Ricevo la stessa storia di NSLog. Ho messo punti di interruzione su viewDidLoad e viewDidAppear nel correctViewController. Si interrompe su viewDidLoad, quindi quando continuo si siede e attende un altro tocco, quindi si interrompe su viewDidAppear. A volte sembra che non abbia bisogno di un tocco ed è basato sul tempo, quando ho esaminato le cose guardando cos'altro viene chiamato, è arrivato il momento di chiamare viewDidLoad.
HalfNormalled

0

Ho scritto l'estensione (categoria) con il metodo swizzling per UIViewController che risolve il problema. Grazie ad AX e NSHipster per i suggerimenti di implementazione ( swift / goal -c ).

Swift

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)
    }
  }
}  

Obiettivo-C

#import <objc/runtime.h>

@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

0

Controlla se la tua cella nello storyboard ha Selezione = nessuna

In tal caso, modificalo in blu o grigio e dovrebbe funzionare


0

XCode Vesion: 9.4.1, Swift 4.1

Nel mio caso ciò accade, quando tocco la cella e mi sposto per un'altra vista. Eseguo il debug nel più profondo e sembra che accada all'interno viewDidAppearperché contiene il seguente codice

if let indexPath = tableView.indexPathForSelectedRow {
   tableView.deselectRow(at: indexPath, animated: true)
}

quindi ho aggiunto il segmento di codice sopra all'interno prepare(for segue: UIStoryboardSegue, sender: Any?)e funziona perfettamente.

Nella mia esperienza, la mia soluzione è: se speriamo di apportare nuove modifiche (ad es. Ricarica della tabella, deseleziona la cella selezionata ecc.) Per la tableview quando torni di nuovo dalla seconda vista, usa delegato invece di viewDidAppear e utilizza il tableView.deselectRowcodice sopra segmento prima di spostare il secondo controller di visualizzazione

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.