presentViewController: animated: YES widok nie pojawi się, dopóki użytkownik nie naciśnie ponownie

93

Mam dziwne zachowanie z presentViewController:animated:completion. To, co tworzę, jest zasadniczo grą w zgadywanie.

Mam UIViewController(frequencyViewController) zawierające UITableView(frequencyTableView). Gdy użytkownik naciśnie wiersz w questionTableView zawierający poprawną odpowiedź, należy utworzyć wystąpienie widoku (correctViewController), a jego widok powinien przesuwać się w górę od dołu ekranu jako widok modalny. To mówi użytkownikowi, że ma poprawną odpowiedź i resetuje frequencyViewController za nią, gotowy do następnego pytania. correctViewController jest odrzucany po naciśnięciu przycisku, aby wyświetlić następne pytanie.

To wszystko poprawnie działa za każdym razem, a pogląd correctViewController pojawiają się natychmiast, o ile presentViewController:animated:completionma animated:NO.

Jeśli animated:YESustawię, correctViewController jest inicjowany i wywołuje viewDidLoad. Jednak viewWillAppear, viewDidAppeari blok uzupełniania z presentViewController:animated:completionnie są wywoływane. Aplikacja po prostu siedzi tam i nadal wyświetla frequencyViewController, dopóki nie zrobię drugiego dotknięcia. Teraz wywoływane są viewWillAppear, viewDidAppear i blok uzupełniania.

Zbadałem trochę więcej i nie tylko kolejne dotknięcie spowoduje kontynuację. Wygląda na to, że jeśli przechylę lub potrząśnie moim iPhonem, może to również spowodować uruchomienie funkcji viewWillLoad itp. To tak, jakby czekał na jakikolwiek inny element danych wejściowych użytkownika, zanim się on rozwinie. Dzieje się to na prawdziwym iPhonie iw symulatorze, co udowodniłem wysyłając polecenie wstrząśnięcia do symulatora.

Naprawdę nie wiem, co z tym zrobić ... Byłbym wdzięczny za każdą pomoc, jakiej ktoś może udzielić.

Dzięki

Oto mój kod. To całkiem proste ...

To jest kod w questionViewController, który działa jako delegat do 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];
        }];
    }
}

To jest całość 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

Edytować:

Znalazłem to pytanie wcześniej: UITableView i presentViewController wymagają 2 kliknięć, aby wyświetlić

A jeśli zmienię didSelectRowkod na ten, działa to bardzo długo z animacją ... Ale jest bałagan i nie ma sensu, dlaczego nie działa w pierwszej kolejności. Więc nie liczę tego jako odpowiedź ...

- (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
źródło
1
Mam ten sam problem. Sprawdziłem w NSLogs, że nie wywołuję żadnych metod, których wykonanie zajmuje dużo czasu. Wygląda na to, że to właśnie animacja presentViewController:ma się uruchamiać z dużym opóźnieniem. Wydaje się, że jest to błąd w iOS 7 i jest również omawiany na forach Apple Dev.
Theo
Mam ten sam problem. Wygląda na błąd iOS7 - nie występuje na iOS6. Również w moim przypadku dzieje się to tylko za pierwszym razem po otwarciu kontrolera widoku prezentacji. @Theo, czy możesz podać łącze do forów Apple Dev?
AX,

Odpowiedzi:

158

Dziś napotkałem ten sam problem. Zagłębiłem się w temat i wygląda na to, że jest to związane z uśpieniem głównej pętli.

W rzeczywistości jest to bardzo subtelny błąd, ponieważ jeśli masz najmniejszą animację sprzężenia zwrotnego, liczniki czasu itp. W kodzie, ten problem nie pojawi się, ponieważ pętla uruchomieniowa będzie utrzymywana przy życiu przez te źródła. Znalazłem problem, używając elementu, UITableViewCellktóry miał selectionStyleustawiony na UITableViewCellSelectionStyleNone, więc żadna animacja wyboru nie wyzwoliła runloop po uruchomieniu programu obsługi wyboru wiersza.

Aby to naprawić (dopóki Apple czegoś nie zrobi), możesz uruchomić główny runloop na kilka sposobów:

Najmniej uciążliwym rozwiązaniem jest zadzwonienie CFRunLoopWakeUp:

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

Lub możesz umieścić pusty blok w kolejce głównej:

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

To zabawne, ale jeśli potrząśniesz urządzeniem, uruchomi ono również główną pętlę (musi przetwarzać zdarzenia ruchu). To samo z kranami, ale to jest zawarte w pierwotnym pytaniu :) Ponadto, jeśli system aktualizuje pasek stanu (np. Aktualizacja zegara, zmiana siły sygnału WiFi itp.), To również obudzi główną pętlę i pokaże widok kontroler.

Dla wszystkich zainteresowanych napisałem minimalny projekt demonstracyjny problemu, aby zweryfikować hipotezę runloopa: https://github.com/tzahola/present-bug

Zgłosiłem również błąd do Apple.

Tamás Zahola
źródło
Dzięki Tamás. To świetne informacje. To wyjaśnia, dlaczego czasami musiałem dotknąć, aby rozpocząć pętlę uruchamiania, a czasami po prostu poczekać i zadziała. Oznaczyłem to jako rozwiązanie, ponieważ wydaje się, że nie możemy nic zrobić, dopóki błąd nie zostanie naprawiony.
HalfNormalled
Jabłko kopie wielką dziurę, wpadam w nią i czuję się bardzo zraniona.
OpenThread
7
OMG, to takie zabawne! BTW, ten błąd nadal istnieje.
igrrik
1
Miałem dokładnie ten sam problem, który był naprawdę irytujący, na szczęście to go rozwiązało.
Mark
1
Potwierdził, że ten problem nadal występuje w iOS 13
Eric Horacek
69

Sprawdź to: https://devforums.apple.com/thread/201431 Jeśli nie chcesz czytać wszystkiego - rozwiązaniem dla niektórych osób (w tym mnie) było wykonanie presentViewControllerpołączenia jawnie w głównym wątku:

Swift 4.2:

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

Cel C:

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

Prawdopodobnie iOS7 psuje wątki didSelectRowAtIndexPath.

TOPÓR
źródło
wow - to zadziałało dla mnie. Ja też widziałem opóźnienia. Nie rozumiem, dlaczego to powinno być konieczne. Dzięki za opublikowanie tego.
drudru
4
Złożyłem radar w Apple na temat tego problemu: rdar: // 19563577 openradar.appspot.com/19563577
mluisbrown
1
Jestem na iOS 8 i to mi nie rozwiązuje - zawsze zgłasza, że ​​jestem w głównym wątku, ale nadal widzę opisany problem.
Robert
7

Pominąłem to w Swift 3.0, używając następującego kodu:

DispatchQueue.main.async { 
    self.present(UIViewController(), animated: true, completion: nil)
}
znak
źródło
1
rozwiązuje problem. Miałem ten sam problem, ponieważ dzwoniłem presentw sprawie oddzwonienia dotyczącego zamknięcia.
Jeremy Piednoel
2

Wywołanie [viewController view]prezentowanego kontrolera widoku załatwiło sprawę.

JaganY
źródło
To zadziałało dla mnie na iOS 8. Myślę, że jest lepsze niż dispatch_async.
Robert,
0

Byłbym ciekawy, co [self setUpViewForNextQuestion];robi.

Możesz spróbować zadzwonić [self.correctViewController.view setNeedsDisplay];pod koniec swojego bloku ukończenia presentViewController.

Nacięcie
źródło
W tej chwili setUpViewForNextQuestion po prostu wywołuje reloadData w widoku tabeli (aby zmienić wszystkie kolory tła z powrotem na biały, jeśli którykolwiek został ustawiony na czerwony przez nieprawidłowe przypuszczenie). Ale czy jakiekolwiek zmiany miałyby jakiś wpływ? Problem polega na tym, że correctViewController nie pojawia się, dopóki użycie nie zostanie ponownie dotknięte, więc ten blok uzupełniania jest wywoływany dopiero po drugim dotknięciu.
HalfNormalled
Spróbowałem twojej sugestii, ale nie zrobiło to żadnej różnicy. Jak mówię, widok się nie pojawia, więc blok uzupełniania jest wywoływany dopiero po drugim stuknięciu lub gestie potrząśnięcia.
HalfNormalled
hmm. na początek użyłbym punktów przerwania, aby potwierdzić, że kod prezentacji zostanie wywołany dopiero po drugim dotknięciu. (w przeciwieństwie do wywołania przy pierwszym dotknięciu, ale rysowania na ekranie dopiero po jakimś czasie). jeśli to drugie jest prawdą, spróbuj wymusić wystąpienie prezentacji w głównym wątku. Gdy używasz „performSelectorWithDelay: 0”, co zmusza aplikację do czekania do następnej iteracji pętli uruchamiania przed wykonaniem. Może to być związane z wątkami.
Nick
Dzięki, Nick. Jak sprawdzić za pomocą punktów przerwania? W tej chwili używam NSLog, aby sprawdzić, kiedy wywoływana jest każda metoda. Oto, co mam teraz: Stuknij 1 Correct Guess: 1 kHz 18:04:57.173 Frequency[641:60b] [HFBCorrectViewController initWithNibName:bundle:] 18:04:57.177 Frequency[641:60b] [HFBCorrectViewController viewDidLoad] Teraz nic się nie dzieje, dopóki: Stuknij 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
Przepraszam, powinno być bardziej dosadne. Rozumiem, jak używać punktów przerwania. Mam tę samą historię, co w przypadku NSLog. Umieszczam punkty przerwania na viewDidLoad i viewDidAppear w correctViewController. Zrywa się przy viewDidLoad, a kiedy kontynuuję, siada i czeka na kolejne dotknięcie, a następnie przerywa przy viewDidAppear. Czasami wydaje się, że nie wymaga stuknięcia i jest zależne od czasu, zanim przejdę przez rzeczy, patrząc na to, co jeszcze jest wywoływane, można wywołać viewDidLoad.
HalfNormalled
0

Napisałem rozszerzenie (kategorię) ze swizzlingiem metod dla UIViewController, które rozwiązuje problem. Podziękowania dla AX i NSHipster za wskazówki dotyczące implementacji ( swift / objective-c ).

Szybki

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

Cel 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
źródło
0

Sprawdź, czy Twoja komórka w serii ujęć ma Selection = none

Jeśli tak, zmień go na niebieski lub szary i powinien działać

Mkey
źródło
0

XCode Vesion: 9.4.1, Swift 4.1

W moim przypadku dzieje się tak, kiedy dotykam komórki i przechodzę do innego widoku. Debuguję głębiej i wydaje się, że dzieje się to w środku, viewDidAppearponieważ zawiera następujący kod

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

następnie dodałem powyższy segment kodu w środku prepare(for segue: UIStoryboardSegue, sender: Any?)i działa idealnie.

Z mojego doświadczenia wynika, że ​​moim rozwiązaniem jest, jeśli mamy nadzieję wprowadzić jakieś nowe zmiany (np. Przeładowanie tabeli, odznaczenie wybranej komórki itp.) Dla widoku tabeli, gdy ponownie wrócimy z drugiego widoku, użyj delegata zamiast viewDidAppeari używając powyższego tableView.deselectRowkodu segment przed przesunięciem drugiego kontrolera widoku

Sachintha Udara
źródło