Подтвердить что ты не робот

DrawViewHierarchyInRect: afterScreenUpdates: задерживает другие анимации

В моем приложении я использую drawViewHierarchyInRect:afterScreenUpdates:, чтобы получить размытое изображение моего представления (используя Apples UIImage category UIImageEffects).

Мой код выглядит следующим образом:

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
[self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:YES];
UIImage *im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
/* Use im */

Во время разработки я заметил, что многие мои анимации были задержаны после небольшого использования моего приложения, т.е. мои представления начали анимацию после заметной (но менее секунды) паузы по сравнению с новым запуском приложения.

После некоторой отладки я заметил, что простой акт использования drawViewHierarchyInRect:afterScreenUpdates: с обновлением экрана, установленным на YES, вызвал эту задержку. Если это сообщение не было отправлено во время сеанса использования, задержка так и не появилась. Использование параметра NO для параметра обновления экрана также заставило задержку исчезнуть.

Странно, что этот код размытия совершенно не связан (насколько я могу судить) с задержкой анимации. Анимации, о которых идет речь, не используют drawViewHierarchyInRect:afterScreenUpdates:, это анимация CAKeyframeAnimation. Простое действие отправки этого сообщения (с обновлениями экрана, установленным на YES), похоже, повлияло на анимацию в моем приложении во всем мире.

Что происходит?

(Я создал видео, иллюстрирующие эффект: с и без задержки анимации. Обратите внимание на задержку появления речевого пузыря "Проверка!" в панели навигации.)

UPDATE

Я создал примерный проект, чтобы проиллюстрировать эту потенциальную ошибку. https://github.com/timarnold/AnimationBugExample

ОБНОВЛЕНИЕ № 2

Я получил ответ от Apple, подтверждающий, что это ошибка. См. Ответ ниже.

4b9b3361

Ответ 1

Я использовал один из моих билетов на поддержку Apple, чтобы спросить Apple о моей проблеме.

Оказывается, это подтвержденная ошибка (радар номер 17851775). Их гипотеза о том, что происходит, ниже:

Метод drawViewHierarchyInRect: afterScreenUpdates: как можно больше выполняет свои операции на графическом процессоре, и большая часть этой работы, вероятно, произойдет за пределами вашего адресного пространства приложений в другом процессе. Передача YES в качестве параметра afterScreenUpdates: drawViewHierarchyInRect: afterScreenUpdates: приведет к тому, что Core Animation очистит все свои буферы в вашей задаче и в задаче рендеринга. Как вы можете себе представить, в этих случаях также существует множество других внутренних вещей. Техника теоретизирует, что это может быть ошибкой в ​​этом механизме, связанном с эффектом, который вы видите.

Для сравнения, метод renderInContext: выполняет свои операции внутри вашего адресного пространства приложений и не использует процесс на основе GPU для выполнения работы. По большей части это другой путь кода, и если он работает для вас, то это подходящее решение. Этот маршрут не так эффективен, как не использует задачу на основе GPU. Кроме того, это не так точно для захвата экрана, поскольку это может исключать размытия и другие функции Core Animation, которые управляются задачей GPU.

И они также обеспечили обходное решение. Они предположили, что вместо:

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
[self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:YES];
UIImage *im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
/* Use im */

Я должен сделать это

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
/* Use im */

Надеюсь, это поможет кому-то!

Ответ 2

Вы пробовали использовать свой код в фоновом потоке? Вот пример использования gcd:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    //background thread
    UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
    [self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:YES];
    UIImage *im = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    dispatch_sync(dispatch_get_main_queue(), ^(void) {
        //update ui on main thread
    });
});

Ответ 3

Если для параметра afterScreenUpdates установлено значение YES, система должна ждать, пока все ожидающие обновления экрана не произойдут, прежде чем он сможет отобразить представление.

Если вы одновременно запускаете анимацию, возможно, рендеринг и анимация пытаются произойти вместе, и это вызывает задержку.

Возможно, стоит поэкспериментировать с тем, чтобы немного ускорить анимацию, чтобы предотвратить это. Очевидно, не слишком много позже, потому что это победит объект, но небольшой промежуток отправки будет стоить.

Ответ 4

Почему у вас есть эта строка (из вашего примера приложения):

animation.beginTime = CACurrentMediaTime();

Просто удалите его, и все будет так, как вы этого хотите.

Установив время анимации явно на CACurrentMediaTime(), вы игнорируете возможные преобразования времени, которые могут присутствовать в дереве слоев. Либо не установите его вообще (по умолчанию анимация запустится now) или используйте метод преобразования времени:

animation.beginTime = [view.layer convert​Time:CACurrentMediaTime() from​Layer:nil];

UIKit добавляет временные преобразования в дерево слоев, когда вы вызываете afterScreenUpdates:YES, чтобы предотвратить переходы в текущей анимации, что было бы вызвано в противном случае промежуточными транзакциями CoreAnimation. Если вы хотите запустить анимацию в определенное время (не now), используйте метод преобразования времени, упомянутый выше.