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

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

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

Фон

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

После изучения предмета я узнал, что мне нужно два типа кода обработки прерываний, чтобы обеспечить хороший ux: "мгновенный" и "плавный". У меня есть метод resetAnimation, который мгновенно восстанавливает свойства представления в исходное состояние, а метод pauseAnimation, который быстро анимируется в одно и то же состояние, с дополнительной меткой с надписью "paused", которая исчезает в верхней части представления.

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

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

Этот случай работает хорошо и сглаженно, реализуя метод делегирования applicationWillResignActive в приложении App Delegate и вызывая pauseAnimation оттуда. Я обращаюсь с возвратом из этого меню многозадачности, реализуя метод делегирования applicationDidBecomeActive и вызывая оттуда мой метод resumeAnimation, который исчезает из "приостановленной" метки, если он есть, и запускает мою анимационную последовательность из начального состояния.

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

Посещение флипсайд

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

  • (пустоты) viewDidDisappear: (BOOL) анимированные

  • (пустоты) viewDidAppear: (BOOL) анимированные

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

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

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

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

В принципе, что мне нужно, какой точный момент делает система для моментального снимка? И, следовательно, каков был бы правильный метод делегата или обработчик уведомлений, чтобы подготовить мой взгляд на получение "сувенирной фотографии"?

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


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

Здесь мой метод pauseAnimation - resetAnimation почти идентичен, за исключением того, что его анимационный вызов имеет нулевую продолжительность и задержку, и он не показывает метку "Приостановил". Одна из причин, по которой я использую UIAnimation для reset, вместо того, чтобы просто назначать новые значения, заключается в том, что по какой-то причине анимация просто не прекращалась, если я не использовал 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
    }
}
4b9b3361

Ответ 1

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

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

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

Надеюсь, это поможет, Fabian


Обновление: -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
}

Ответ 2

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

В заключение: у Kreiser было все правильно: iOS берет скриншот сразу после метода applicationDidEnterBackground: возвращает - сразу же значение, до конца текущей runloop.

Это означает, что если вы запускаете запланированные задачи any в методе didEnterBackground, который хотите завершить до выхода, вам придется разрешить текущую runloop работать до тех пор, пока задачи могут выполняться отделка.

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

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

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

Ответ 3

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

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

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

Лучше всего использовать

[CATransaction flush]

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

Ответ 4

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

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

Ответ 5

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