У меня странное поведение с presentViewController:animated:completion
. То, что я делаю, - это, по сути, игра в угадывание.
У меня есть UIViewController
(frequencyViewController), содержащий UITableView
(frequencyTableView). Когда пользователь нажимает на строку в questionTableView, содержащую правильный ответ, должно быть создано представление (correViewController), и его представление должно скользить вверх из нижней части экрана, как модальное представление. Это сообщает пользователю, что у него есть правильный ответ, и сбрасывает стоящий за ним frequencyViewController, готовый к следующему вопросу. RightViewController отключается при нажатии кнопки, чтобы открыть следующий вопрос.
Все это работает правильно каждый раз, и правильное представление ViewController появляется мгновенно, пока оно presentViewController:animated:completion
есть animated:NO
.
Если я установил animated:YES
, инициализируется правильноViewController и обращается к viewDidLoad
. Однако viewWillAppear
, viewDidAppear
и блок завершения из presentViewController:animated:completion
не вызываются. Приложение просто сидит и показывает frequencyViewController, пока я не сделаю второе касание. Теперь вызываются viewWillAppear, viewDidAppear и блок завершения.
Я исследовал еще немного, и это не просто еще одно нажатие, которое заставит его продолжить. Кажется, если я наклоню или встряхну свой iPhone, это также может привести к срабатыванию viewWillLoad и т. Д. Это похоже на ожидание любого другого бита пользовательского ввода, прежде чем он будет прогрессировать. Это происходит на реальном iPhone и в симуляторе, что я доказал, отправив на симулятор команду встряхивания.
Я действительно не понимаю, что с этим делать ... Я был бы очень признателен за любую помощь, которую мог бы предоставить.
Благодарность
Вот мой код. Это довольно просто ...
Это код в questionViewController, который действует как делегат для 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];
}];
}
}
Это весь правильный 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
Редактировать:
Я нашел этот вопрос ранее: UITableView и PresentViewController требуют 2 щелчка для отображения
И если я изменю свой didSelectRow
код на этот, он будет работать очень долго с анимацией ... Но он беспорядочный и не имеет смысла в отношении того, почему он вообще не работает. Так что я не считаю это ответом ...
- (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];
}];
}
источник
presentViewController:
должна запускаться с большой задержкой. Кажется, это ошибка в iOS 7, которая также обсуждается на форумах Apple Dev.Ответы:
Сегодня я столкнулся с той же проблемой. Я покопался в теме и кажется, что это связано со спящим основным циклом выполнения.
На самом деле это очень тонкая ошибка, потому что, если у вас есть малейшая анимация обратной связи, таймеры и т.д. в вашем коде, эта проблема не появится, потому что цикл выполнения будет поддерживаться этими источниками. Я обнаружил проблему, используя a, для
UITableViewCell
которого былоselectionStyle
установлено значениеUITableViewCellSelectionStyleNone
, чтобы анимация выбора не запускала цикл выполнения после запуска обработчика выбора строки.Чтобы исправить это (пока Apple что-то не сделает), вы можете запустить основной цикл выполнения несколькими способами:
Наименее назойливое решение - позвонить
CFRunLoopWakeUp
:[self presentViewController:vc animated:YES completion:nil]; CFRunLoopWakeUp(CFRunLoopGetCurrent());
Или вы можете поставить пустой блок в основную очередь:
[self presentViewController:vc animated:YES completion:nil]; dispatch_async(dispatch_get_main_queue(), ^{});
Забавно, но если вы встряхнете устройство, он также запустит основной цикл (он должен обрабатывать события движения). То же самое с касаниями, но это включено в исходный вопрос :) Кроме того, если система обновляет строку состояния (например, обновляются часы, изменяется сила сигнала WiFi и т. Д.), Это также разбудит основной цикл и представит представление контроллер.
Для всех, кого это интересует, я написал минимальный демонстрационный проект проблемы, чтобы проверить гипотезу runloop: https://github.com/tzahola/present-bug
Я также сообщил об ошибке в Apple.
источник
Проверьте это: https://devforums.apple.com/thread/201431 Если вы не хотите все это читать - решение для некоторых людей (включая меня) заключалось в том, чтобы сделать
presentViewController
вызов явно в основном потоке:Swift 4.2:
DispatchQueue.main.async { self.present(myVC, animated: true, completion: nil) }
Цель-C:
dispatch_async(dispatch_get_main_queue(), ^{ [self presentViewController:myVC animated:YES completion:nil]; });
Вероятно, iOS7 портит потоки
didSelectRowAtIndexPath
.источник
Я обошел его в Swift 3.0, используя следующий код:
DispatchQueue.main.async { self.present(UIViewController(), animated: true, completion: nil) }
источник
present
в обратный вызов закрытия.Вызов
[viewController view]
представленного контроллера представления помог мне.источник
Было бы любопытно посмотреть, что
[self setUpViewForNextQuestion];
делает.Вы можете попробовать позвонить
[self.correctViewController.view setNeedsDisplay];
в конце блока завершения вpresentViewController
.источник
Correct Guess: 1 kHz 18:04:57.173 Frequency[641:60b] [HFBCorrectViewController initWithNibName:bundle:] 18:04:57.177 Frequency[641:60b] [HFBCorrectViewController viewDidLoad]
Теперь ничего не происходит, пока: Tap 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]
Я написал расширение (категорию) с переключением методов для UIViewController, которое решает проблему. Спасибо AX и NSHipster за подсказки по реализации ( swift / objective-c ).
Swift
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) } } }
Цель-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
источник
Проверьте, есть ли у вашей ячейки в раскадровке Selection = none
Если да, измените его на синий или серый, и он должен работать.
источник
XCode Vesion: 9.4.1, Swift 4.1
В моем случае это происходит, когда я нажимаю на ячейку и перехожу к другому виду. Я отлаживаю глубже, и кажется, что это происходит внутри,
viewDidAppear
потому что содержит следующий кодif let indexPath = tableView.indexPathForSelectedRow { tableView.deselectRow(at: indexPath, animated: true) }
затем я добавил вышеупомянутый сегмент кода внутри
prepare(for segue: UIStoryboardSegue, sender: Any?)
и отлично работал.По моему опыту, мое решение: если мы надеемся внести какие-либо новые изменения (например, перезагрузить таблицу, отменить выбор выбранной ячейки и т. Д.) Для табличного представления, когда снова вернемся из второго представления, тогда используйте делегат вместо
viewDidAppear
и с использованиемtableView.deselectRow
кода выше сегмент перед перемещением контроллера второго представленияисточник