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:completion
ma animated:NO
.
Jeśli animated:YES
ustawię, correctViewController jest inicjowany i wywołuje viewDidLoad
. Jednak viewWillAppear
, viewDidAppear
i blok uzupełniania z presentViewController:animated:completion
nie 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ę didSelectRow
kod 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];
}];
}
źródło
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.Odpowiedzi:
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,
UITableViewCell
który miałselectionStyle
ustawiony naUITableViewCellSelectionStyleNone
, 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.
źródło
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
presentViewController
połą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
.źródło
Pominąłem to w Swift 3.0, używając następującego kodu:
DispatchQueue.main.async { self.present(UIViewController(), animated: true, completion: nil) }
źródło
present
w sprawie oddzwonienia dotyczącego zamknięcia.Wywołanie
[viewController view]
prezentowanego kontrolera widoku załatwiło sprawę.źródło
Byłbym ciekawy, co
[self setUpViewForNextQuestion];
robi.Możesz spróbować zadzwonić
[self.correctViewController.view setNeedsDisplay];
pod koniec swojego bloku ukończeniapresentViewController
.źródło
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]
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
źródło
Sprawdź, czy Twoja komórka w serii ujęć ma Selection = none
Jeśli tak, zmień go na niebieski lub szary i powinien działać
źródło
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,
viewDidAppear
ponieważ zawiera następujący kodif 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
viewDidAppear
i używając powyższegotableView.deselectRow
kodu segment przed przesunięciem drugiego kontrolera widokuźródło