presentViewController: анімований: ТАК подання не з’явиться, доки користувач знову не натисне


93

У мене виникає якась дивна поведінка presentViewController:animated:completion. Що я роблю, це по суті гра в здогадки.

У мене є UIViewController(frequencyViewController), що містить UITableView(frequencyTableView). Коли користувач натискає на рядок, що містить questionTableView, що містить правильну відповідь, слід створити екземпляр подання (correctViewController), а його подання повинно ковзати з нижньої частини екрана як модальний вигляд. Це повідомляє користувачеві, що вони мають правильну відповідь, і скидає FrequViewController за ним, готовий до наступного питання. pravilViewController відхиляється натисканням кнопки, щоб виявити наступне питання.

Все це працює правильно кожного разу, і правильний виглядViewController відображається миттєво стільки, скільки presentViewController:animated:completionбуло animated:NO.

Якщо я встановив цей параметр animated:YES, правильнийViewController ініціалізується та здійснює дзвінки в viewDidLoad. Тим НЕ менше viewWillAppear, viewDidAppearі блок завершення від presentViewController:animated:completionне називаються. Додаток просто сидить там і досі показує frequencyViewController, доки я не здійсню другий тап. Тепер викликаються viewWillAppear, viewDidAppear та блок завершення.

Я дослідив трохи більше, і це не просто черговий кран, який змусить його продовжуватись. Здається, якщо я нахилити або похитнути свій iPhone, це також може призвести до його запуску viewWillLoad і т. Д. Це все одно, що він чекає будь-якого іншого шматочка входу користувача, перш ніж він прогресує. Це відбувається на справжньому iPhone та в тренажері, що я довів, надіславши команду shake на симулятор.

Я справді не знаю, що з цим робити ... Я б дуже вдячний будь-якій допомозі, яку будь-хто може надати.

Дякую

Ось мій код. Це досить просто ...

Це код у 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];
    }];
}

1
У мене така сама проблема. Я перевірив за допомогою NSLogs, що я не називаю жодних методів, які потребують тривалого часу для виконання. Здається, що просто анімація, яка presentViewController:повинна запускатися, починається з великою затримкою. Здається, це помилка в iOS 7, а також обговорюється на Форумах Apple Dev.
Тео

У мене така сама проблема. Схоже, помилка iOS7 - не відбувається на iOS6. Крім того, в моєму випадку це відбувається лише в перший раз після відкриття контролера подання подання. @ Theo, ти можеш надати посилання на форуми Apple Dev?
AX

Відповіді:


158

Я зіткнувся з такою ж проблемою сьогодні. Я заглибився в тему, і, здається, це пов’язано з тим, що спить головний цикл.

Насправді це дуже тонка помилка, тому що якщо у вас є найменша анімація зворотного зв’язку, таймери тощо, у вашому коді ця проблема не з’явиться, тому що запущений цикл буде підтримуватися цими джерелами. Я виявив проблему, використовуючи для 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.


Спасибі Тамаш. Це чудова інформація. Це пояснює, чому іноді мені потрібно було натиснути, щоб розпочати цикл запуску, а іноді просто почекати, і це спрацювало. Я позначив це як рішення, оскільки, схоже, не існує нічого іншого, поки помилка не виправлена.
HalfNormalled

Яблуко копає чудову яму, я падаю в неї і відчуваю сильне поранення.
OpenThread

7
OMG, це так весело! До речі, ця помилка все ще існує.
igrrik 02

1
У мене точно така ж проблема була по-справжньому прикрою, на щастя, це виправлено.
Марк

1
Підтверджено, що ця проблема все ще виникає в iOS 13
Ерік Горачек

69

Перевірте це: https://devforums.apple.com/thread/201431 Якщо ви не хочете читати все це - рішенням для деяких людей (включаючи мене) було зробити presentViewControllerдзвінок явно в основному потоці:

Швидкий 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.


вау - це працювало для мене. Я теж бачив затримки. Я не можу зрозуміти, навіщо це потрібно. Дякуємо, що розмістили це.
drudru

4
Подав радар з Apple про цю проблему: rdar: // 19563577 openradar.appspot.com/19563577
mluisbrown

1
Я працюю на iOS 8, і це не виправляє це для мене - воно завжди повідомляє про те, що в основному потоці, але я все ще бачу описану проблему.
Роберт

7

Я обійшов його в Swift 3.0, використовуючи такий код:

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

1
це вирішує питання. У мене була така ж проблема, тому що я дзвонив presentу зворотній зв'язок із закриттям.
Джеремі П'єднол

2

Заклик [viewController view]до представленого контролера перегляду зробив для мене фокус.


Це працювало у мене на iOS 8. Я думаю, це краще, ніж dispatch_async.
Роберт

0

Мені було б цікаво подивитися, що [self setUpViewForNextQuestion]; робить.

Ви можете спробувати зателефонувати [self.correctViewController.view setNeedsDisplay];в кінці блоку завершення в presentViewController.


Зараз setUpViewForNextQuestion просто викликає reloadData у табличному поданні (щоб змінити всі кольори тла на білі, якщо будь-який з них був встановлений на червоний через неправильну здогадку). Але чи змінилися б якісь зміни? Проблема полягає в тому, що коректнийViewController не з’являється доти, доки користувачі знову не натиснуть, тому цей блок завершення викликається лише після цього другого натискання.
Половинанормалізовано

Я спробував вашу пропозицію, але це не змінило. Як я вже казав, подання не з’являється, тому блок завершення викликається лише після другого натискання або жесту.
HalfNormalled

хм. для початку, я б використовував точки зупинки, щоб підтвердити, що ваш код презентації не викликається до другого натискання. (проти того, щоб викликати з першого натискання, але не відображатиметься на екрані до пізнішого часу). якщо останнє відповідає дійсності, спробуйте змусити вашу презентацію відбуватися в основному потоці. Коли ви використовуєте "performSelectorWithDelay: 0", що змушує програму чекати до наступної ітерації циклу запуску перед виконанням. Це може бути пов’язано з різьбою.
Нік

Дякую, Нік. Як би я перевірив, використовуючи точки прориву? На даний момент я використовую NSLog, щоб перевірити, коли викликається кожен метод. Це те, що я маю зараз: Натисніть 1 Correct Guess: 1 kHz 18:04:57.173 Frequency[641:60b] [HFBCorrectViewController initWithNibName:bundle:] 18:04:57.177 Frequency[641:60b] [HFBCorrectViewController viewDidLoad] Тепер нічого не відбувається, поки: Торкніться 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

Вибачте, мав бути більш чітким. Я розумію, як використовувати точки зупинку. У мене така сама історія, як і з NSLog. Я ставлю точки зупинки на viewDidLoad і viewDidAppear у правильномуViewController. Він ламається на viewDidLoad, потім, коли я продовжую, він сидить і чекає іншого натискання, а потім ламається на viewDidAppear. Іноді здається, що не потрібен натискання, і це залежить від часу, до того моменту, коли я перебрав речі, дивлячись на те, що ще викликається, це вже навколо виклику viewDidLoad.
HalfNormalled

0

Я написав розширення (категорію) із завитками методу для UIViewController, який вирішує проблему. Завдяки AX та NSHipster за підказки щодо впровадження ( swift / target -c ).

Швидкий

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

Ціль-С

#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

0

Перевірте, чи є у вашій комірці розкадрування виділення = немає

Якщо так, змініть його на блакитний або сірий, і він повинен працювати


0

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коду сегмент перед переміщенням другого контролера подання

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.