web-dev-qa-db-fra.com

presentViewController: animé: la vue OUI n'apparaît que lorsque l'utilisateur appuie à nouveau

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 de questionTableView contenant la réponse correcte, une vue (correctViewController) doit être instanciée et sa vue doit glisser vers le haut à partir du bas de l'écran, sous forme d'affichage modal. Cela indique à l'utilisateur qu'il a la bonne réponse et réinitialise le frequencyViewController derrière, prêt pour la question suivante. correctViewController est exclu lorsque vous appuyez sur un bouton pour afficher la question suivante.

Tout cela fonctionne correctement à chaque fois et la vue de correctViewController apparaît instantanément tant que presentViewController:animated:completion a animated:NO.

Si je définis animated:YES, correctViewController est initialisé et effectue des appels vers viewDidLoad. Toutefois, viewWillAppear, viewDidAppear et le bloc d'achèvement de presentViewController:animated:completion ne sont pas appelés. L'application reste affichée en affichant toujours le contrôle frequencyViewController jusqu'à ce que je tape une seconde fois. Maintenant, viewWillAppear, viewDidAppear et le bloc d'achèvement sont appelés.

J'ai enquêté un peu plus, et ce n'est pas juste 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. C'est comme si vous attendiez toute autre entrée de l'utilisateur avant que cela ne progresse. 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 quiconque pourrait fournir.

Merci

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

C'est le code de 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

Modifier:

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

Et si je change mon code didSelectRow en ceci, cela fonctionne très longtemps avec l'animation ... Mais c'est compliqué et cela n'a aucun sens de savoir pourquoi cela ne fonctionne pas. Donc, je ne compte pas cela 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];
    }];
}
85
HalfNormalled

J'ai rencontré le même problème aujourd'hui. J'ai creusé dans le sujet et il semble que cela a un rapport avec le cycle de fonctionnement principal endormi. 

En fait, il s’agit d’un bogue très subtil, car si vous avez la moindre animation en retour, des minuteries, etc. dans votre code, ce problème ne se posera pas, car le runloop sera maintenu en vie par ces sources. J'ai trouvé le problème en utilisant une variable UITableViewCell dont la variable selectionStyle était définie sur UITableViewCellSelectionStyleNone, de sorte qu'aucune animation de sélection ne déclenche le cycle d'exécution après l'exécution du gestionnaire de sélection de ligne. 

Pour résoudre ce problème (jusqu'à ce que Apple fasse quelque chose), vous pouvez déclencher le cycle d'exécution principal de plusieurs manières:

La solution la moins intrusive consiste à 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, il déclenchera également la boucle principale (il doit traiter les événements de mouvement). Même chose avec les taps, mais cela est inclus dans la question initiale :) En outre, si le système met à jour la barre d'état (par exemple, les mises à jour de l'horloge, la force du signal WiFi, etc.), cela réveillera également la boucle principale et présentera la vue. manette. 

Pour les personnes intéressées, j’ai écrit un projet de démonstration minimal du problème afin de vérifier l’hypothèse du cycle de fonctionnement: https://github.com/tzahola/present-bug

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

143
Tamás Zahola

Découvrez ceci: https://devforums.Apple.com/thread/201431 Si vous ne voulez pas tout lire, la solution pour certaines personnes (y compris moi-même) était de passer l'appel presentViewController 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 dérange les threads dans didSelectRowAtIndexPath.

64
AXE

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

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

Appeler [viewController view] sur le contrôleur de vue présenté présentait le truc pour moi.

2
JaganY

J'ai écrit l'extension (catégorie) avec la méthode swizzling pour UIViewController qui résout le problème. Merci à AX et à NSHipster pour les conseils de mise en œuvre ( 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
0
Gladkov_Art

XCode Vesion: 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 dans viewDidAppear à cause 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 dans prepare(for segue: UIStoryboardSegue, sender: Any?) et fonctionne parfaitement.

D'après mon expérience, ma solution est la suivante: si nous souhaitons apporter de nouvelles modifications (par exemple, rechargement de table, désélectionner la cellule sélectionnée, etc.) pour la vue de table lorsque vous revenez de la deuxième vue, utilisez délégué, au lieu de viewDidAppear, et utilisez la méthode ci-dessus tableView.deselectRow segment de code avant de déplacer le second contrôleur de vue

0
Sachintha Udara

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 dans presentViewController.

0
Nick

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

Si oui, changez-le en bleu ou gris et cela devrait fonctionner

0
Mkey