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];
}];
}
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.
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
.
Je l'ai contourné dans Swift 3.0 en utilisant le code suivant:
DispatchQueue.main.async {
self.present(UIViewController(), animated: true, completion: nil)
}
Appeler [viewController view]
sur le contrôleur de vue présenté présentait le truc pour moi.
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
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
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
.
Vérifiez si votre cellule dans le storyboard a Selection = none
Si oui, changez-le en bleu ou gris et cela devrait fonctionner