presentViewController: animiert: Die JA-Ansicht wird erst angezeigt, wenn der Benutzer erneut tippt

93

Ich bekomme ein seltsames Verhalten mit presentViewController:animated:completion. Was ich mache, ist im Wesentlichen ein Ratespiel.

Ich habe einen UIViewController(frequenzViewController), der einen UITableView(frequenzTableView) enthält. Wenn der Benutzer auf die Zeile in questionTableView tippt, die die richtige Antwort enthält, sollte eine Ansicht (korrektViewController) instanziiert und ihre Ansicht als modale Ansicht vom unteren Bildschirmrand nach oben verschoben werden. Dies teilt dem Benutzer mit, dass er eine richtige Antwort hat, und setzt den dahinter liegenden frequenzViewController für die nächste Frage zurück. Der korrekte ViewController wird auf Knopfdruck geschlossen, um die nächste Frage anzuzeigen.

Dies alles funktioniert jedes Mal korrekt und die Ansicht des korrekten ViewControllers wird sofort angezeigt, solange dies der Fall presentViewController:animated:completionist animated:NO.

Wenn ich setze animated:YES, wird korrekter ViewController initialisiert und ruft auf viewDidLoad. Allerdings viewWillAppear, viewDidAppearund der Abschluss - Block aus presentViewController:animated:completionnicht aufgerufen werden . Die App sitzt nur da und zeigt immer noch den Frequenz-ViewController an, bis ich ein zweites Mal tippe. Jetzt werden viewWillAppear, viewDidAppear und der Abschlussblock aufgerufen.

Ich habe ein bisschen mehr nachgeforscht, und es ist nicht nur ein weiterer Tipp, der dazu führt, dass es weitergeht. Wenn ich mein iPhone neige oder schüttle, kann dies auch dazu führen, dass es viewWillLoad usw. auslöst. Es ist, als würde es auf andere Benutzereingaben warten, bevor es fortschreitet. Dies geschieht auf einem echten iPhone und im Simulator, was ich durch Senden des Shake-Befehls an den Simulator bewiesen habe.

Ich bin wirklich ratlos, was ich dagegen tun soll ... Ich würde mich über jede Hilfe freuen, die jemand leisten kann.

Vielen Dank

Hier ist mein Code. Es ist ziemlich einfach ...

Dies ist Code in questionViewController, der als Delegat für questionTableView fungiert

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

Dies ist der gesamte korrekte ViewController

@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

Bearbeiten:

Ich habe diese Frage früher gefunden: UITableView und presentViewController benötigen 2 Klicks, um angezeigt zu werden

Und wenn ich meinen didSelectRowCode in diesen Code ändere , funktioniert es sehr schnell mit Animationen ... Aber es ist chaotisch und macht keinen Sinn, warum es überhaupt nicht funktioniert. Also zähle ich das nicht als Antwort ...

- (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];
    }];
}
HalfNormalled
quelle
1
Ich habe das gleiche Problem. Ich habe bei NSLogs überprüft, dass ich keine Methoden aufrufe, deren Ausführung lange dauert. Es scheint, dass nur die Animation, presentViewController:die ausgelöst werden soll, mit einer großen Verzögerung beginnt. Dies scheint ein Fehler in iOS 7 zu sein und wird auch in den Apple Dev Forums behandelt.
Theo
Ich habe das gleiche Problem. Scheint ein iOS7-Fehler zu sein - tritt unter iOS6 nicht auf. In meinem Fall geschieht dies auch nur beim ersten Öffnen nach dem Öffnen des Präsentationsansichts-Controllers. @Theo, können Sie einen Link zu den Apple Dev Forums bereitstellen?
AX

Antworten:

156

Ich bin heute auf das gleiche Problem gestoßen. Ich habe mich mit dem Thema befasst und es scheint, dass es damit zusammenhängt, dass der Haupt-Runloop schläft.

Eigentlich ist es ein sehr subtiler Fehler, denn wenn Sie die geringste Feedback-Animation, Timer usw. in Ihrem Code haben, tritt dieses Problem nicht auf, da der Runloop von diesen Quellen am Leben erhalten wird. Ich habe das Problem durch die Verwendung eines gefunden, auf UITableViewCelldas selectionStylegesetzt wurde UITableViewCellSelectionStyleNone, sodass keine Auswahlanimation den Runloop auslöste, nachdem der Zeilenauswahl-Handler ausgeführt wurde.

Um das Problem zu beheben (bis Apple etwas unternimmt), können Sie den Haupt-Runloop auf verschiedene Arten auslösen:

Die am wenigsten aufdringliche Lösung besteht darin, Folgendes anzurufen CFRunLoopWakeUp:

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

Oder Sie können einen leeren Block in die Hauptwarteschlange einreihen:

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

Es ist lustig, aber wenn Sie das Gerät schütteln, wird auch die Hauptschleife ausgelöst (es muss die Bewegungsereignisse verarbeiten). Das Gleiche gilt für Taps, aber das ist in der ursprünglichen Frage enthalten :) Wenn das System die Statusleiste aktualisiert (z. B. die Uhr aktualisiert, die WLAN-Signalstärke ändert sich usw.), wird auch die Hauptschleife aufgeweckt und die Ansicht angezeigt Regler.

Für alle Interessierten habe ich ein minimales Demonstrationsprojekt des Problems geschrieben, um die Runloop-Hypothese zu überprüfen: https://github.com/tzahola/present-bug

Ich habe den Fehler auch Apple gemeldet.

Tamás Zahola
quelle
Danke Tamás. Das sind großartige Informationen. Es erklärt, warum ich manchmal tippen musste, um die Laufschleife zu starten, oder manchmal einfach warten musste und es funktionierte. Ich habe dies als Lösung markiert, da es anscheinend nichts anderes gibt, was wir tun können, bis der Fehler behoben ist.
HalfNormalled
Apple gräbt eine tolle Grube, ich falle hinein und fühle mich schwer verletzt.
OpenThread
7
OMG, das ist so lustig! Übrigens, dieser Fehler besteht immer noch.
igrrik
1
Ich hatte genau das gleiche Problem war wirklich nervig, zum Glück hat dies das Problem behoben.
Mark
1
Bestätigt, dass dieses Problem immer noch in iOS 13 auftritt
Eric Horacek
69

Überprüfen Sie dies: https://devforums.apple.com/thread/201431 Wenn Sie nicht alles lesen möchten, bestand die Lösung für einige Leute (einschließlich mich) darin, den presentViewControllerAnruf explizit im Hauptthread zu tätigen :

Swift 4.2:

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

Ziel c:

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

Wahrscheinlich bringt iOS7 die Threads durcheinander didSelectRowAtIndexPath.

AXT
quelle
wow - das hat bei mir funktioniert. Ich habe auch die Verzögerungen gesehen. Ich kann nicht verstehen, warum dies notwendig sein sollte. Vielen Dank für die Veröffentlichung.
Drudru
4
Hat ein Radar bei Apple über dieses Problem eingereicht: rdar: // 19563577 openradar.appspot.com/19563577
mluisbrown
1
Ich bin auf iOS 8 und dies behebt es nicht für mich - es wird immer berichtet, dass es sich im Haupt-Thread befindet, aber ich sehe immer noch das beschriebene Problem.
Robert
7

Ich habe es in Swift 3.0 mit dem folgenden Code umgangen:

DispatchQueue.main.async { 
    self.present(UIViewController(), animated: true, completion: nil)
}
Kennzeichen
quelle
1
es löst das Problem. Ich hatte das gleiche Problem, weil ich presenteinen Schließungsrückruf anrief .
Jeremy Piednoel
2

Das Aufrufen [viewController view]des vorgestellten View Controllers hat mir geholfen.

JaganY
quelle
Dies funktionierte für mich unter iOS 8. Ich denke, es ist besser als dispatch_async.
Robert
0

Ich wäre gespannt was [self setUpViewForNextQuestion]; macht.

Sie können versuchen, [self.correctViewController.view setNeedsDisplay];am Ende Ihres Abschlussblocks anzurufen presentViewController.

Nick
quelle
Im Moment ruft setUpViewForNextQuestion nur reloadData in der Tabellenansicht auf (um alle Hintergrundfarben wieder auf Weiß zu ändern, wenn eine durch eine falsche Vermutung auf Rot gesetzt wurde). Aber würden Änderungen dort einen Unterschied machen? Das Problem ist, dass der korrekte ViewController erst angezeigt wird, wenn die Verwendung erneut getippt wird. Daher wird dieser Abschlussblock erst nach diesem zweiten Tippen aufgerufen.
HalfNormalled
Ich habe Ihren Vorschlag ausprobiert, aber es machte keinen Unterschied. Wie gesagt, die Ansicht wird nicht angezeigt, sodass der Abschlussblock erst nach dem zweiten Tippen oder der Schüttelgeste aufgerufen wird.
HalfNormalled
hmm. Für den Anfang würde ich Haltepunkte verwenden, um zu bestätigen, dass Ihr Präsentationscode erst beim zweiten Tippen aufgerufen wird. (versus beim ersten Tippen aufgerufen zu werden, aber erst zu einem späteren Zeitpunkt auf dem Bildschirm gezeichnet zu werden). Wenn letzteres zutrifft, versuchen Sie, Ihre Präsentation im Hauptthread zu erzwingen. Wenn Sie "performSelectorWithDelay: 0" verwenden, wird die App gezwungen, vor der Ausführung bis zur nächsten Iteration der Ausführungsschleife zu warten. Es könnte Threading-bezogen sein.
Nick
Danke, Nick. Wie würde ich anhand von Haltepunkten prüfen? Im Moment verwende ich NSLog, um zu überprüfen, wann jede Methode aufgerufen wird. Das habe ich jetzt: Tippe auf 1 Correct Guess: 1 kHz 18:04:57.173 Frequency[641:60b] [HFBCorrectViewController initWithNibName:bundle:] 18:04:57.177 Frequency[641:60b] [HFBCorrectViewController viewDidLoad] Jetzt passiert nichts mehr bis: 18: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]
Tippe auf
Entschuldigung, hätte expliziter sein sollen. Ich verstehe, wie man Haltepunkte verwendet. Ich bekomme die gleiche Geschichte wie bei NSLog. Ich habe Haltepunkte auf viewDidLoad und viewDidAppear im korrekten ViewController gesetzt. Es wird bei viewDidLoad unterbrochen. Wenn ich fortfahre, sitzt es und wartet auf ein weiteres Tippen. Anschließend wird es bei viewDidAppear unterbrochen. Manchmal scheint es keinen Tipp zu benötigen und es ist zeitbasiert. Wenn ich die Dinge durchgesehen habe, um zu sehen, was sonst noch aufgerufen wird, ist es rund um den Aufruf von viewDidLoad.
HalfNormalled
0

Ich habe eine Erweiterung (Kategorie) mit einer Methode für UIViewController geschrieben, die das Problem löst. Vielen Dank an AX und NSHipster für die Implementierungshinweise ( swift / objectiv-c ).

Schnell

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

Ziel 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
quelle
0

Überprüfen Sie, ob Ihre Zelle im Storyboard Auswahl = keine hat

Wenn ja, ändern Sie es in blau oder grau und es sollte funktionieren

Mkey
quelle
0

XCode Vesion: 9.4.1, Swift 4.1

In meinem Fall passiert dies, wenn ich auf Zelle tippe und zur anderen Ansicht wechsle. Ich debugge in die Tiefe und es scheint, dass im Inneren passieren, viewDidAppearweil folgenden Code enthält

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

dann habe ich oben das Code-Segment hinzugefügt prepare(for segue: UIStoryboardSegue, sender: Any?)und arbeite perfekt.

Nach meiner Erfahrung lautet meine Lösung: Wenn wir hoffen, neue Änderungen (z. B. erneutes Laden der Tabelle, Deaktivieren der ausgewählten Zelle usw.) für die Tabellenansicht vorzunehmen, wenn Sie wieder aus der zweiten Ansicht zurückkehren, verwenden Sie delegate anstelle des viewDidAppearobigen tableView.deselectRowCodes Segment vor dem Verschieben des zweiten Ansichtsreglers

Sachintha Udara
quelle