presentViewController: animé: la vue OUI n'apparaîtra pas jusqu'à ce que l'utilisateur appuie à nouveau

93

J'ai un comportement étrange avec presentViewController:animated:completion. Ce que je fais est essentiellement un jeu de devinettes.

J'ai un UIViewController(frequencyViewController) contenant un UITableView(frequencyTableView). Lorsque l'utilisateur appuie sur la ligne en questionTableView contenant la bonne réponse, une vue (correctViewController) doit être instanciée et sa vue doit glisser vers le haut à partir du bas de l'écran, sous la forme d'une vue modale. Cela indique à l'utilisateur qu'il a une réponse correcte et réinitialise le FrequencyViewController derrière lui, prêt pour la question suivante. correctViewController est rejeté en appuyant sur un bouton pour révéler la question suivante.

Tout cela fonctionne correctement à chaque fois, et la vue correcteViewController apparaît instantanément aussi longtemps que l' presentViewController:animated:completiona animated:NO.

Si je définis animated:YES, correctViewController est initialisé et fait des appels à viewDidLoad. Cependant viewWillAppear, viewDidAppearet le bloc d'achèvement de presentViewController:animated:completionne sont pas appelés. L'application reste juste là et affiche encore frequencyViewController jusqu'à ce que je fasse un deuxième tap. Maintenant, viewWillAppear, viewDidAppear et le bloc de complétion sont appelés.

J'ai enquêté un peu plus, et ce n'est pas simplement un autre robinet qui le fera continuer. Il semble que si j'incline ou secoue mon iPhone, cela peut également provoquer le déclenchement de viewWillLoad, etc. Cela se produit sur un vrai iPhone et dans le simulateur, ce que j'ai prouvé en envoyant la commande shake au simulateur.

Je ne sais vraiment pas quoi faire à ce sujet ... J'apprécierais vraiment toute aide que n'importe qui peut me fournir.

Merci

Voici mon code. C'est assez simple ...

Il s'agit du code en questionViewController qui agit en tant que délégué de 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];
        }];
    }
}

C'est l'ensemble du correctViewController

@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

Éditer:

J'ai trouvé cette question plus tôt: UITableView et presentViewController prennent 2 clics pour s'afficher

Et si je change mon didSelectRowcode pour cela, cela fonctionne très souvent avec l'animation ... Mais c'est compliqué et ne comprend pas pourquoi cela ne fonctionne pas en premier lieu. Donc je ne compte pas ça comme une réponse ...

- (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];
    }];
}
Demi-normalisé
la source
1
J'ai le même problème. J'ai vérifié avec NSLogs que je n'appelle aucune méthode qui prend du temps à s'exécuter. Il semble que ce soit juste l'animation qui presentViewController:est censée déclencher commence avec un gros retard. Cela semble être un bogue dans iOS 7 et est également discuté dans les forums Apple Dev.
Theo
J'ai le même problème. Cela ressemble à un bogue iOS7 - ne se produit pas sur iOS6. De plus, dans mon cas, cela ne se produit que la première fois après avoir ouvert le contrôleur de présentation. @Theo, pouvez-vous fournir un lien vers les forums Apple Dev?
AX

Réponses:

158

J'ai rencontré le même problème aujourd'hui. J'ai creusé dans le sujet et il semble que cela soit lié au sommeil de la boucle principale.

En fait, c'est un bogue très subtil, car si vous avez la moindre animation de rétroaction, les minuteries, etc. dans votre code, ce problème ne se posera pas car la boucle d'exécution sera maintenue en vie par ces sources. J'ai trouvé le problème en utilisant un UITableViewCellqui avait son selectionStyleensemble à UITableViewCellSelectionStyleNone, de sorte qu'aucune animation de sélection a déclenché le runloop après le gestionnaire de sélection de ligne RAN.

Pour résoudre ce problème (jusqu'à ce qu'Apple fasse quelque chose), vous pouvez déclencher la boucle d'exécution principale de plusieurs manières:

La solution la moins intrusive est d'appeler CFRunLoopWakeUp:

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

Ou vous pouvez mettre en file d'attente un bloc vide dans la file d'attente principale:

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

C'est drôle, mais si vous secouez l'appareil, cela déclenchera également la boucle principale (il doit traiter les événements de mouvement). Même chose avec les robinets, mais c'est inclus dans la question d'origine :) De plus, si le système met à jour la barre d'état (par exemple, l'horloge se met à jour, la force du signal WiFi change, etc.), cela réveillera également la boucle principale et présentera la vue manette.

Pour toute personne intéressée, j'ai écrit un projet de démonstration minimal du problème pour vérifier l'hypothèse de la boucle d'exécution: https://github.com/tzahola/present-bug

J'ai également signalé le bogue à Apple.

Tamás Zahola
la source
Merci Tamás. C'est une excellente information. Cela explique pourquoi parfois j'avais besoin de taper pour lancer la boucle de course, ou parfois simplement d'attendre et cela fonctionnerait. J'ai marqué cela comme la solution car il ne semble pas y avoir autre chose que nous puissions faire jusqu'à ce que le bogue soit corrigé.
HalfNormalled le
Apple creuse une grande fosse, je tombe dedans et me sens mal.
OpenThread
7
OMG, c'est tellement hilarant! BTW, ce bogue existe toujours.
igrrik le
1
J'ai eu exactement le même problème était vraiment ennuyeux, heureusement, cela l'a résolu.
Mark
1
Confirmé que ce problème persiste dans iOS 13
Eric Horacek
69

Vérifiez ceci: https://devforums.apple.com/thread/201431 Si vous ne voulez pas tout lire - la solution pour certaines personnes (y compris moi) était de faire l' presentViewControllerappel explicitement sur le fil principal:

Swift 4.2:

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

Objectif c:

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

Probablement iOS7 gâche les fils didSelectRowAtIndexPath.

HACHE
la source
wow - cela a fonctionné pour moi. Je voyais aussi les retards. Je ne peux pas comprendre pourquoi cela devrait être nécessaire. Merci d'avoir publié ceci.
drudru
4
A déposé un radar auprès d'Apple à propos de ce problème: rdar: // 19563577 openradar.appspot.com/19563577
mluisbrown
1
Im sur iOS 8 et cela ne résout pas le problème pour moi - il rapporte toujours être sur le fil principal, mais je vois toujours le problème décrit.
Robert
7

Je l'ai contourné dans Swift 3.0 en utilisant le code suivant:

DispatchQueue.main.async { 
    self.present(UIViewController(), animated: true, completion: nil)
}
marque
la source
1
cela résout le problème. J'ai eu le même problème parce que j'appelais presentun rappel de fermeture.
Jeremy Piednoel
2

L'appel [viewController view]sur le contrôleur de vue présenté a fait l'affaire pour moi.

JaganY
la source
Cela a fonctionné pour moi sur iOS 8. Je pense que c'est mieux que dispatch_async.
Robert
0

Je serais curieux de voir ce que [self setUpViewForNextQuestion];fait.

Vous pouvez essayer d'appeler [self.correctViewController.view setNeedsDisplay];à la fin de votre bloc d'achèvement presentViewController.

pseudo
la source
À l'heure actuelle, setUpViewForNextQuestion appelle simplement reloadData sur la tableview (pour changer toutes les couleurs d'arrière-plan en blanc si l'une d'entre elles avait été définie sur rouge par une estimation incorrecte). Mais y aurait-il des changements qui feraient une différence? Le problème est que correctViewController n'apparaît pas tant que l'utilisation n'est pas à nouveau activée, ce bloc d'achèvement n'est donc appelé qu'après ce deuxième tap.
HalfNormalled le
J'ai essayé votre suggestion, mais cela n'a fait aucune différence. Comme je l'ai dit, la vue n'apparaît pas, donc le bloc de complétion n'est appelé qu'après le deuxième appui ou le geste de secousse.
HalfNormalled le
hmm. pour commencer, j'utiliserais des points d'arrêt pour confirmer que votre code de présentation n'est pas appelé avant le 2ème tap. (par opposition à être appelé au premier tap, mais à ne pas être dessiné à l'écran avant une date ultérieure). si ce dernier est vrai, essayez de forcer votre présentation à se produire sur le thread principal. Lorsque vous utilisez «performSelectorWithDelay: 0», cela force l'application à attendre la prochaine itération de la boucle d'exécution avant de s'exécuter. Cela peut être lié au filetage.
Nick
Merci, Nick. Comment vérifier à l'aide de points d'arrêt? En ce moment, j'utilise NSLog pour vérifier quand chaque méthode est appelée. Voici ce que j'ai maintenant: Appuyez sur 1 Correct Guess: 1 kHz 18:04:57.173 Frequency[641:60b] [HFBCorrectViewController initWithNibName:bundle:] 18:04:57.177 Frequency[641:60b] [HFBCorrectViewController viewDidLoad] Maintenant, rien ne se passe avant: Appuyez sur 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
Désolé, aurait dû être plus explicite. Je comprends comment utiliser les points d'arrêt. J'ai la même histoire qu'avec NSLog. J'ai mis des points d'arrêt sur viewDidLoad et viewDidAppear dans le correctViewController. Il se brise sur viewDidLoad, puis quand je continue, il s'assied et attend un autre tap, puis s'arrête sur viewDidAppear. Parfois, il semble ne pas avoir besoin d'un robinet et il est basé sur le temps, au moment où j'ai parcouru les choses en regardant ce qui est appelé d'autre, il est arrivé à appeler viewDidLoad.
HalfNormalled le
0

J'ai écrit une extension (catégorie) avec une méthode swizzling pour UIViewController qui résout le problème. Merci à AX et NSHipster pour les conseils d'implémentation ( swift / objective-c ).

Rapide

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

Objectif 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
Gladkov_Art
la source
0

Vérifiez si votre cellule dans le storyboard a Selection = none

Si c'est le cas, changez-le en bleu ou en gris et cela devrait fonctionner

Mkey
la source
0

Version XCode: 9.4.1, Swift 4.1

Dans mon cas, cela se produit lorsque je tape sur la cellule et que je passe à une autre vue. Je débogue dans le plus profond et il semble que cela se produise à l'intérieur à viewDidAppearcause de contient le code suivant

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

puis j'ai ajouté le segment de code ci-dessus à l'intérieur prepare(for segue: UIStoryboardSegue, sender: Any?)et fonctionne parfaitement.

D'après mon expérience, ma solution est la suivante: si nous espérons faire de nouvelles modifications (par exemple, recharger la table, désélectionner la cellule sélectionnée, etc.) pour la vue de la table lorsque vous revenez de la deuxième vue, utilisez alors déléguer au lieu viewDidAppearet en utilisant le tableView.deselectRowcode ci-dessus segment avant de déplacer le deuxième contrôleur de vue

Sachintha Udara
la source