Точный момент, когда iOS делает снимок вида при вводе фона? - PullRequest
21 голосов
/ 29 октября 2011

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

Фон

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

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

Двойной щелчок по кнопке выхода

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

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

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

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

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

  • (void) viewDidDisappear: (BOOL) animated

  • (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
    }
}

Ответы [ 5 ]

21 голосов
/ 28 ноября 2011

Снимок экрана делается сразу после возврата этого метода. Я предполагаю, что ваш метод -resetAnimation завершается в следующем цикле цикла выполнения, а не сразу. Я не пробовал этого, но вы можете попробовать запустить 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 голосов
/ 27 февраля 2015

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

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

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

Лучшим способом является использование

[CATransaction flush]

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

2 голосов
/ 06 декабря 2011

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

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

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

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

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

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

1 голос
/ 28 ноября 2011

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

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

0 голосов
/ 15 апреля 2014

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

...