iOS, перезапуск анимации при выходе из фона - PullRequest
20 голосов
/ 18 мая 2011

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

[UIView animateWithDuration:60 delay:0 options:(UIViewAnimationOptionCurveLinear |UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState) animations:^{
    [bg setFrame:CGRectMake(0, 0, 1378, 1005)];
} completion:nil];

Я пытался поместить заданный кадр перед анимацией, но это просто делает его более быстрым.

[bg setFrame:CGRectMake(0, 0, 1378, 1005)];

есть идеи?

Ответы [ 9 ]

27 голосов
/ 11 декабря 2012

Вы можете добавить наблюдателя в своем классе для UIApplicationWillEnterForegroundNotification:

- (void)addNotifications { 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil]; 
}

- (void)applicationWillEnterForeground { 
    [self animate]; 
}

- (void)animate { 
    [bg setFrame:CGRectMake(0, 0, 0, 0)]; 
    [UIView animateWithDuration:60 delay:0 options:(UIViewAnimationOptionCurveLinear |UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState) animations:^{
        [bg setFrame:CGRectMake(0, 0, 1378, 1005)];
    } completion:nil];
}

Важно установить начальное состояние анимации (и не забудьте удалить наблюдателя уведомления)

26 голосов
/ 18 мая 2011

хорошо, ответ @ dany_23 мог бы сработать.

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

в

- (void)applicationWillResignActive:(UIApplication *)application

вы вызываете метод в вашем viewcontroller, который реализует следующий код.

[view.layer removeAllAnimations];
 // this following CGRect is the point where your view originally started 
[bg setFrame:CGRectMake(0, 0, 1378, 1005)]; 

, а в

- (void)applicationDidBecomeActive:(UIApplication *)application

вы вызываете метод вваш viewcontroller, который просто запускает анимацию.что-то вроде

[UIView animateWithDuration:60 delay:0 options:(UIViewAnimationOptionCurveLinear |UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState) animations:^{
      [bg setFrame:CGRectMake(0, 0, 1378, 1005)];
} completion:nil];

Надеюсь, это поможет, спасибо всем, кто ответил.

7 голосов
/ 12 мая 2017

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

Для Swift 3 вы можете подкласс этого класса:

class ViewWithPersistentAnimations : UIView {
    private var persistentAnimations: [String: CAAnimation] = [:]
    private var persistentSpeed: Float = 0.0

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.commonInit()
    }

    func commonInit() {
        NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil)
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    func didBecomeActive() {
        self.restoreAnimations(withKeys: Array(self.persistentAnimations.keys))
        self.persistentAnimations.removeAll()
        if self.persistentSpeed == 1.0 { //if layer was plaiyng before backgorund, resume it
            self.layer.resume()
        }
    }

    func willResignActive() {
        self.persistentSpeed = self.layer.speed

        self.layer.speed = 1.0 //in case layer was paused from outside, set speed to 1.0 to get all animations
        self.persistAnimations(withKeys: self.layer.animationKeys())
        self.layer.speed = self.persistentSpeed //restore original speed

        self.layer.pause()
    }

    func persistAnimations(withKeys: [String]?) {
        withKeys?.forEach({ (key) in
            if let animation = self.layer.animation(forKey: key) {
                self.persistentAnimations[key] = animation
            }
        })
    }

    func restoreAnimations(withKeys: [String]?) {
        withKeys?.forEach { key in
            if let persistentAnimation = self.persistentAnimations[key] {
                self.layer.add(persistentAnimation, forKey: key)
            }
        }
    }
}

extension CALayer {
    func pause() {
        if self.isPaused() == false {
            let pausedTime: CFTimeInterval = self.convertTime(CACurrentMediaTime(), from: nil)
            self.speed = 0.0
            self.timeOffset = pausedTime
        }
    }

    func isPaused() -> Bool {
        return self.speed == 0.0
    }

    func resume() {
        let pausedTime: CFTimeInterval = self.timeOffset
        self.speed = 1.0
        self.timeOffset = 0.0
        self.beginTime = 0.0
        let timeSincePause: CFTimeInterval = self.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
        self.beginTime = timeSincePause
    }
}

Он позаботится о приостановке всех ваших анимаций в текущем состоянии и их повторном добавлении, когда приложение выходит из фона - без их сброса.

Суть: https://gist.github.com/grzegorzkrukowski/a5ed8b38bec548f9620bb95665c06128

4 голосов
/ 18 мая 2011

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

2 голосов
/ 01 марта 2019

@ Ответ Гжегожа Круковского для меня сработало. Я просто хотел добавить, что если вам не нужно возобновлять анимацию с того места, где она остановилась, а имитировать ее работу в фоновом режиме, вы можете просто пропустить эти строки:

let timeSincePause: CFTimeInterval = self.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
self.beginTime = timeSincePause

из функции resume() в расширении CALayer и оставьте для beginTime значение 0.0.

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

2 голосов
/ 18 мая 2011

Хорошо, если я правильно понял вопрос, вы пытаетесь создать анимацию и остановить ее в середине.

В этом случае я бы предложил вам использовать некоторую переменную «state» для хранения состояния анимации (например, свойство frame). Эту переменную вы можете использовать в дальнейшем коде, чтобы поместить анимируемый объект в правильное положение. Это можно сделать в функции animationDidStop ( animationDidStop: закончено: ). Для реализации этой функции вам нужно будет использовать делегирование. Это означает установить AppNameViewController (или некоторый другой уникальный объект для всего приложения) в качестве делегата для анимации и записать реализацию в его m-файл. Это позволит вам запустить код «Настройка положения» в конце анимации.

Следующая задача - сохранить состояние анимации. При запуске анимации платформа Core Animation создает группу промежуточных слоев (слоев представления). И эти слои отображаются во время анимации, а затем они удаляются. И когда анимация завершает выполнение, объект просто переводится в конечное состояние. На самом деле вы должны установить это, когда создаете так называемую «явную анимацию» (и если вы этого не сделаете, анимация будет воспроизводиться, но объект в конце отскочит). Каждый из этих слоев представления имеет набор или собственную копию всех свойств, которые называются «анимируемые свойства». Когда в качестве ключа для анимации установлено одно из свойств анимации, Core Animation вызывает (BOOL) needsDisplayForKey , который возвращает YES / NO. Он сообщает Core Animation, требуется ли для изменения указанного ключа повторное отображение слоя. «Повторное отображение» означает вызов метода drawInContext для уровня представления. Здесь вы можете получить свойства слоя презентации, который отображается в данный момент, и поместить их в переменную состояния.

Анимация воспроизводится в потоке, и для задания переменной состояния я использовал делегирование. Требуется создать подкласс CALayer (скажем, AnimLayer) и определить в нем протокол только с одним методом, который хранит «состояние». Этот метод имеет один параметр, который является состоянием. Затем я реализовал метод протокола, который сохраняет состояние в классе AnimLayer, и установил уникальный объект в качестве делегата. Таким образом, эти слои представления (копии AnimLayer) сами не хранят состояние. Вместо этого значение «состояние», переданное в качестве параметра функции-делегату, сохраняется уникальным объектом в главном потоке.

Я сделал что-то подобное, но моя задача была немного другой. Я не знаю более простой способ, извините. Надеюсь, это поможет.

1 голос
/ 12 июня 2014

Этот способ будет лучше, просто зарегистрируйтесь на ApplicationDelegate, и он станет методом и будет следить за статусом.

- (void)pauseAnimate{
    CFTimeInterval pausedTime = [self.layer timeOffset];
    self.layer.speed = 1.0;
    self.layer.timeOffset = 0.0;
    self.layer.beginTime = 0.0;
    CFTimeInterval timeSincePause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    self.layer.beginTime = timeSincePause;
}

- (void)stopAnimate{
    CFTimeInterval pausedTime = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
    self.layer.speed = 0.0;
    self.layer.timeOffset = pausedTime;
}
0 голосов
/ 29 апреля 2019

Начиная с выпуска swift5.

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

Просто звоню ...

view.layer.removeAllAnimations()

не хватило, и новая анимация не запускалась. Мне тоже пришлось бежать ...

view.transform = .identity

Я подозреваю, что вы можете установить любое новое значение или сохранить предыдущее состояние и установить его.

0 голосов
/ 18 мая 2011

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

...