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

Если это так, измените его на синий или серый, и он должен работать