Точный момент, когда iOS делает снимок экрана при входе в фоновый режим?

У меня проблема с переводом приложения iPhone в фоновый режим нажатием кнопки выхода, а затем повторным запуском нажатием значка запуска на главном экране: представление приложения возвращается в исходное состояние, как я этого хочу, но перед этим оно мигает ненадолго появляется на экране предыдущее неправильное состояние просмотра.

Фон

Мое основное представление состоит в основном из последовательности взаимосвязанных вызовов UIAnimateWithDuration. Поведение, которое я хочу всякий раз, когда происходит какое-либо прерывание, состоит в том, чтобы сбросить анимацию в исходное состояние (если только все анимации не закончились и приложение не вошло в статическую заключительную фазу) и начинать оттуда всякий раз, когда приложение возвращается в активное и видимое состояние .

Изучив предмет, я понял, что мне нужны два типа кода обработки прерываний, чтобы обеспечить хороший UX: «мгновенный» и «гладкий». У меня есть метод resetAnimation, который мгновенно сбрасывает свойства представления в начальное состояние, и метод pauseAnimation, который быстро анимирует до того же состояния, с дополнительной меткой с надписью «приостановлено», исчезающей в верхней части представления.

Двойное нажатие кнопки выхода

Причиной этого является вариант использования «двойного нажатия кнопки выхода», который на самом деле не скрывает ваш вид и не переводит вас в фоновое состояние, он просто немного прокручивается вверх, чтобы показать меню многозадачности внизу. Таким образом, мгновенный сброс состояния просмотра в этом случае выглядел очень некрасиво. Анимированный переход и сообщение пользователю, что вы приостановлены, казались лучшей идеей.

Этот случай работает хорошо и плавно, реализуя метод делегата applicationWillResignActive в моем делегате приложения и вызывая паузуAnimation оттуда. Я обрабатываю возврат из этого многозадачного меню, реализуя метод делегата applicationDidBecomeActive и вызывая оттуда мой метод возобновления анимации, который исчезает с метки «приостановлено», если она есть, и запускает мою последовательность анимации из начального состояния.

Все работает нормально, нигде не мерцает.

Посещение обратной стороны

Мое приложение построено на основе шаблона «утилиты» Xcode, поэтому оно имеет перевернутый вид для отображения информации/настроек. Я обрабатываю посещение обратной стороны и возвращение обратно к основному представлению, реализуя эти два метода делегата в моем контроллере основного представления:

  • (void) viewDidDisappear: (BOOL) анимированный

  • (void) viewDidAppear: (BOOL) анимированный

Я вызываю свой resetAnimation в методе viewDidDisappear и возобновляю анимацию в viewDidAppear. Это все прекрасно работает, основной вид - это его начальное состояние с самого начала перехода в видимое состояние - никаких неожиданных мельканий неправильных состояний анимации чего-либо. Но:

Нажатие кнопки выхода и перезапуск со значка моего приложения (глючная часть!)

Здесь начинаются проблемы. Когда я нажимаю кнопку выхода один раз, и мое приложение начинает переход в фоновый режим, происходят две вещи. Во-первых, здесь также вызывается applicationWillResignActive, поэтому мой метод pauseAnimation также запускается. В этом нет необходимости, поскольку переход здесь не должен быть плавным — вид просто становится статичным и «уменьшается», открывая главный экран — но что вы можете сделать? Что ж, не будет никакого вреда, если я просто вызову resetAnimation до точного момента, когда система сделает снимок представления.

В любом случае, во-вторых, вызывается applicationDidEnterBackground в делегате приложения. Я попытался вызвать resetAnimation оттуда, чтобы представление было в правильном состоянии, когда приложение вернется, но это, похоже, не работает. Кажется, что «моментальный снимок» уже сделан, и поэтому, когда я нажимаю значок запуска приложения и перезапускаю его, неправильное состояние просмотра ненадолго мигает на экране, прежде чем отобразится правильное исходное состояние. После этого он работает нормально, анимация идет так, как и должна, но это уродливое мерцание в момент перезапуска не исчезнет, ​​что бы я ни пытался.

Вообще меня интересует, в какой именно момент система делает этот снимок? И, следовательно, каким будет правильный метод делегата или обработчик уведомлений для подготовки моего представления к съемке «сувенирной фотографии»?

PS. Затем есть default.png, который, кажется, отображается не только при первом запуске, но и всякий раз, когда процессор испытывает трудности или возврат к приложению ненадолго задерживается по какой-либо другой причине. Это немного некрасиво, особенно если вы возвращаетесь к своему обратному виду, который полностью отличается от вашего вида по умолчанию. Но это такая основная функция iOS, я думаю, мне даже не стоит пытаться понять или контролировать ее :)


Изменить: поскольку люди просили реальный код, а мое приложение уже было выпущено после того, как я задал этот вопрос, я опубликую некоторые из них здесь. (Приложение называется Sweetest Kid, и если вы хотите увидеть, как оно на самом деле работает, оно здесь: http://itunes.apple.com/app/sweetest-kid/id476637106?mt=8 )

Вот мой метод pauseAnimation — resetAnimation почти идентичен, за исключением того, что его вызов анимации имеет нулевую продолжительность и задержку, и он не показывает метку «Приостановлено». Одна из причин, по которой я использую UIAnimation для сброса значений вместо простого присвоения новых значений, заключается в том, что по какой-то причине анимация просто не останавливалась, если я не использовал UIAnimation. Во всяком случае, вот метод pauseAnimation:

    - (void)pauseAnimation {
    if (currentAnimationPhase < 6 || currentAnimationPhase == 255) { 
            // 6 means finished, 255 is a short initial animation only showing at first launch
        self.paused = YES;
        [UIView animateWithDuration:0.3
                              delay:0 
                            options:UIViewAnimationOptionAllowUserInteraction |
         UIViewAnimationOptionBeginFromCurrentState |
         UIViewAnimationOptionCurveEaseInOut |
         UIViewAnimationOptionOverrideInheritedCurve |
         UIViewAnimationOptionOverrideInheritedDuration
                         animations:^{
                             pausedView.alpha = 1.0;
                             cameraImageView.alpha = 0;
                             mirrorGlowView.alpha = 0;
                             infoButton.alpha = 1.0;
                             chantView.alpha = 0; 
                             verseOneLabel.alpha = 1.0;
                             verseTwoLabel.alpha = 0; 
                             verseThreeLabel.alpha = 0;
                             shine1View.alpha = stars1View.alpha = stars2View.alpha = 0;
                             shine1View.transform = CGAffineTransformIdentity;
                             stars1View.transform = CGAffineTransformIdentity;
                             stars2View.transform = CGAffineTransformIdentity;
                             finishedMenuView.alpha = 0;
                             preparingMagicView.alpha = 0;}
                         completion:^(BOOL finished){
                             pausedView.alpha = 1.0;
                             cameraImageView.alpha = 0;
                             mirrorGlowView.alpha = 0;
                             infoButton.alpha = 1.0;
                             chantView.alpha = 0; 
                             verseOneLabel.alpha = 1.0;
                             verseTwoLabel.alpha = 0; 
                             verseThreeLabel.alpha = 0;
                             shine1View.alpha = stars1View.alpha = stars2View.alpha = 0;
                             shine1View.transform = CGAffineTransformIdentity;
                             stars1View.transform = CGAffineTransformIdentity;
                             stars2View.transform = CGAffineTransformIdentity;
                             finishedMenuView.alpha = 0;
                             preparingMagicView.alpha = 0;
                         }];
        askTheMirrorButton.enabled = YES; 
        againButton.enabled = NO;
        shareOnFacebookButton.enabled = NO;
        emailButton.enabled = NO;
        saveButton.enabled = NO;
        currentAnimationPhase = 0;
        [[cameraImageView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; // To remove the video preview layer
    }
}

person Samuli Viitasaari    schedule 29.10.2011    source источник
comment
К сожалению, готов поспорить, что Apple не хочет, чтобы вы знали/зависели от того, когда сделан снимок. Они могут изменить это в будущем. +1 в этом. В некоторых случаях у них есть опции, позволяющие загружать определенное изображение по умолчанию (например, при получении push-уведомления), но было бы лучше, если бы у вас был больший контроль.   -  person David Hodge    schedule 27.11.2011
comment
У меня тоже была такая беда. ​​stackoverflow.com/questions/4659394/ Хотелось бы увидеть решение.   -  person Daniel Schneller    schedule 28.11.2011
comment
Почему вы не можете использовать механику паузы/возобновления, которая у вас уже есть для многозадачного лотка (два нажатия кнопки выхода, как вы это называете), когда пользователь выходит в Springboard (одно нажатие кнопки)?   -  person Jon Grant    schedule 28.11.2011
comment
@JonGrant: Потому что pauseAnimation — это анимация продолжительностью 0,3 секунды, и она никогда не сможет завершиться, поскольку однократное нажатие кнопки выхода просто немедленно останавливает процессы переднего плана приложения. Кроме того, pauseAnimation необходимо вызывать из applicationWillResignActive для получения желаемого эффекта, и в этот момент нет способа узнать, будет ли приложение переходить в applicationDidEnterBackground или нет (другими словами, если мы действительно переходим в фоновый режим или просто обрабатываем временный перерыв).   -  person Samuli Viitasaari    schedule 29.11.2011


Ответы (5)


Скриншот делается сразу после возврата этого метода. Я предполагаю, что ваш метод -resetAnimation завершается в следующем цикле выполнения, а не сразу. Я не пробовал это, но вы можете попытаться запустить цикл выполнения, а затем вернуться немного позже:

- (void) applicationDidEnterBackground:(UIApplication *)application {
    // YOUR CODE HERE

    // Let the runloop run for a brief moment
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}

Я надеюсь, что это поможет, Фабиан


Обновление: различия -pauseAnimation и -resetAnimation

Подход: задержите анимацию, происходящую в -applicationWillResignActive: и отмените отложенную анимацию в -applicationDidEnterBackground:

- (void) applicationWillResignActive:(UIApplication *)application {
    // Measure the time between -applicationWillResignActive: and -applicationDidEnterBackground first!
    [self performSelector:@selector(pauseAnimation) withObject:nil afterDelay:0.1];

    // OTHER CODE HERE
}

- (void) applicationDidEnterBackground:(UIApplication *)application {
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(pauseAnimation) object:nil];

    // OTHER CODE HERE
}
person Fabian Kreiser    schedule 28.11.2011
comment
Также было бы полезно знать, что вы делаете в -resetAnimation. - person Fabian Kreiser; 28.11.2011
comment
Спасибо, я попробую это. Ваше предположение кажется обоснованным, и это может быть так, поскольку я использую UIAnimateWithDuration (с задержкой 0 и длительностью 0, но, тем не менее, с методом анимации) в моем методе resetAnimation. Вскоре я обновлю свой вопрос некоторыми фрагментами кода. - person Samuli Viitasaari; 29.11.2011
comment
Кайзер: ваш ответ может оказаться ключом к обнаружению ошибки в моей исходной логике. Решая случай с двойным щелчком кнопки выхода, я заметил, что текущая анимация останавливается только при переназначении новых значений моим анимируемым свойствам внутри другого вызова анимации по какой-то причине. - person Samuli Viitasaari; 29.11.2011
comment
И я просто адаптировал тот же стиль к варианту использования в фоновом режиме, но теперь, когда я об этом думаю, не должно быть никаких причин использовать там UIAnimation, просто сразу сбросьте значения. Это также должно устранить вторичный поток и, вероятно, также автоматически дать мне правильное изображение снимка. Я попробую именно это, как только доберусь до своего dev comp. - person Samuli Viitasaari; 29.11.2011
comment
Да, если вы используете UIViewAnimation, вы должны удалить это и сбросить только свои представления и иерархию представлений. - person Fabian Kreiser; 29.11.2011
comment
Однако проблема с различием -pauseAnimation и -resetAnimation до сих пор не решена. Возможно, вам следует измерить время между -willResignActive и -didEnterBackground, когда вы выходите из приложения с помощью кнопки «Домой», а затем в -willResignActive запускать анимацию с этой продолжительностью в качестве задержки (скорее всего, это будет очень короткий временной интервал) и отменить отложенный огонь в -didEnterBackground. Я отредактирую свой ответ. - person Fabian Kreiser; 29.11.2011

Теперь я провел несколько тестов и устранил проблему благодаря @Fabian Kreiser.

В заключение: Крейзер был прав: iOS делает снимок экрана сразу после того, как метод applicationDidEnterBackground: возвращает значение — немедленно, то есть до окончания текущего цикла выполнения.

Это означает, что если вы запускаете какие-либо запланированные задачи в методе didEnterBackground, которые хотите завершить перед выходом, вам придется позволить текущему циклу выполнения работать столько времени, сколько может потребоваться для завершения задач.

В моем случае запланированной задачей был вызов метода UIAnimateWithDuration — меня смутил тот факт, что и его задержка, и продолжительность были равны 0 — вызов, тем не менее, был запланирован для выполнения в другом потоке и, следовательно, не мог закончить до окончания метода applicationDidEnterBackground. Результат: снимок экрана действительно был сделан до того, как дисплей был обновлен до нужного мне состояния, и при повторном запуске этот снимок экрана ненадолго мигал на экране, вызывая нежелательное мерцание.

Кроме того, чтобы обеспечить «плавное» и «мгновенное» поведение перехода, объясненное в моем вопросе, предложение Крейзера отложить вызов «плавного» перехода в applicationWillResignActive: и отменить вызов в applicationDidEnterBackground: отлично работает. Я заметил, что задержка между двумя методами делегата в моем случае составляла около 0,005-0,019 секунды, поэтому я применил большой запас и использовал задержку 0,05 секунды.

Моя награда, галочка с правильным ответом, и моя благодарность Фабиану. Надеюсь, это поможет и другим в подобной ситуации.

person Samuli Viitasaari    schedule 06.12.2011

Решение runloop на самом деле приводит к некоторым проблемам с приложением.

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];

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

Лучший способ - использовать

[CATransaction flush]

Это приводит к немедленному применению всех текущих транзакций и устраняет проблему, приводящую к черному экрану.

person Steven    schedule 27.02.2015

В зависимости от того, насколько важно для вас, чтобы этот переход прошел гладко, вы можете полностью отключить многозадачность для своего приложения с помощью UIApplicationExitsOnSuspend. Тогда вам гарантирован ваш Default.png и чистый визуальное состояние.

Конечно, вам придется сохранять/восстанавливать состояние при выходе/запуске, и без дополнительной информации о характере вашего приложения трудно сказать, стоит ли это того.

person Ben Mosher    schedule 28.11.2011
comment
Спасибо за идею, я не знал о такой возможности. И все же я все еще надеюсь, что есть какой-то «более чистый» способ достижения этой цели. - person Samuli Viitasaari; 29.11.2011

В iOS 7 есть вызов [[UIApplication sharedApplication] ignoreSnapshotOnNextApplicationLaunch], который делает именно то, что вам нужно.

person vedrano    schedule 15.04.2014
comment
Приятно знать, что дела пошли вперед, пока я отсутствовал на сцене iOS! - person Samuli Viitasaari; 06.05.2014