PresentViewController: анимированный: YES вид не появится, пока пользователь не повторит
Я получаю странное поведение с presentViewController:animated:completion
. То, что я делаю, - это, по сути, игра с угадыванием.
У меня есть UIViewController
(frequencyViewController), содержащий UITableView
(frequencyTableView). Когда пользователь нажимает на строку в вопросеTableView, содержащую правильный ответ, представление (correctViewController) должно создаваться, и его представление должно скользить вверху снизу экрана в виде модального представления. Это говорит пользователю, что у них правильный ответ, и сбрасывает частоту ViewController позади нее, готового к следующему вопросу. correctViewController отклоняется нажатием кнопки, чтобы показать следующий вопрос.
Это все работает правильно каждый раз, и представление правильногоViewController появляется мгновенно, пока presentViewController:animated:completion
имеет animated:NO
.
Если я установил animated:YES
, correctViewController инициализируется и вызывает вызовы viewDidLoad
. Однако viewWillAppear
, viewDidAppear
и блок завершения из presentViewController:animated:completion
не вызываются. Приложение просто сидит там, пока показывается frequencyViewController, пока я не сделаю второй ответ. Теперь вызывается viewWillAppear, viewDidAppear и блок завершения.
Я исследовал немного больше, и это не просто другой кран, который заставит его продолжить. Кажется, если я наклоняю или встряхиваю свой iPhone, это также может привести к запуску viewWillLoad и т.д. Это похоже на то, что он ждет любой другой бит пользовательского ввода до того, как он будет прогрессировать. Это происходит на реальном iPhone и в симуляторе, что я доказал, отправив команду shake на симулятор.
Я действительно не понимаю, что с этим делать... Я бы очень признателен за любую помощь, которую любой может предоставить.
Спасибо
Вот мой код. Это довольно просто...
Это код в вопросеViewController, который выступает в качестве делегата в вопросеTableView
- (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];
}];
}
}
Это весь правильный_контроль
@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
Edit:
Я нашел этот вопрос раньше: 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];
}];
}
Ответы
Ответ 1
Сегодня я столкнулся с той же проблемой. Я вникнул в эту тему, и, похоже, это связано с тем, что основная runloop спала.
На самом деле это очень тонкая ошибка, потому что, если у вас есть малейшая анимация обратной связи, таймеры и т.д. в вашем коде, эта проблема не будет отображаться, потому что runloop будет поддерживаться этими источниками. Я нашел проблему, используя UITableViewCell
, у которой был установлен selectionStyle
на UITableViewCellSelectionStyleNone
, так что анимация выбора не запускала runloop после запуска обработчика выбора строки.
Чтобы исправить это (пока Apple ничего не делает), вы можете запустить основную runloop несколькими способами:
Наименее навязчивым решением является вызов CFRunLoopWakeUp
:
[self presentViewController:vc animated:YES completion:nil];
CFRunLoopWakeUp(CFRunLoopGetCurrent());
Или вы можете вставить пустой блок в основную очередь:
[self presentViewController:vc animated:YES completion:nil];
dispatch_async(dispatch_get_main_queue(), ^{});
Это смешно, но если вы встряхнете устройство, это также вызовет основной цикл (он должен обработать события движения). То же самое с кранами, но это включено в исходный вопрос:) Также, если система обновляет строку состояния (например, обновления часов, сила сигнала Wi-Fi меняется и т.д.), Которые также пробудят основной цикл и представляют представление контроллер.
Для всех желающих я написал минимальный демонстрационный проект для проверки гипотезы runloop: https://github.com/tzahola/present-bug
Я также сообщил об ошибке Apple.
Ответ 2
Проверьте это: https://devforums.apple.com/thread/201431
Если вы не хотите читать все это - решение для некоторых людей (включая меня) должно было сделать вызов presentViewController
явно в основном потоке:
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:myVC animated:YES completion:nil];
});
Вероятно, iOS7 запутывает потоки в didSelectRowAtIndexPath
.
Ответ 3
Я обошел его в Swift 3.0, используя следующий код:
DispatchQueue.main.async {
self.present(UIViewController(), animated: true, completion: nil)
}
Ответ 4
Вызов [viewController view]
на представленном контроллере представления помогло.
Ответ 5
Мне было бы интересно узнать, что делает [self setUpViewForNextQuestion];
.
Вы можете попробовать позвонить [self.correctViewController.view setNeedsDisplay];
в конце вашего блока завершения в presentViewController
.
Ответ 6
Я написал расширение (категория) с методом swizzling для 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)
}
}
}
objective-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
Ответ 7
Проверьте, есть ли у вашей ячейки в раскадровке Selection = none
Если это так, измените его на синий или серый, и он должен работать