iOS, Restarting animation when coming out of the background
Solution 1
well the answer of @dany_23 could work.
But I came across an other method that works just fine if you don't need to resume your animation but restart your animation, without the view or layer snapping when you reactivate the app.
in the
- (void)applicationWillResignActive:(UIApplication *)application
you call a method in your viewcontroller which implements the following code.
[view.layer removeAllAnimations];
// this following CGRect is the point where your view originally started
[bg setFrame:CGRectMake(0, 0, 1378, 1005)];
and in the
- (void)applicationDidBecomeActive:(UIApplication *)application
you call a method in your viewcontroller that just starts the animation. something like
[UIView animateWithDuration:60 delay:0 options:(UIViewAnimationOptionCurveLinear |UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState) animations:^{
[bg setFrame:CGRectMake(0, 0, 1378, 1005)];
} completion:nil];
Hope this helps, Thanks to all who replied.
Solution 2
You can add an observer in your class for 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];
}
It is important to set the begin state of the animation (and don't forget to remove the notification observer)
Solution 3
There is a better soloution here than restarting whole animation each time you come from background.
For Swift 3 you can subclass this class:
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
}
}
It will take care of pausing all your animations in current state and re-adding it when app comes from background - without resetting them.
Gist: https://gist.github.com/grzegorzkrukowski/a5ed8b38bec548f9620bb95665c06128
Solution 4
You'll have to pause the animation when your app goes into the background and resume it when it becomes active again. You can find some sample code here that describes how to pause and resume an animation.
Solution 5
@Grzegorz Krukowski's answer worked like a charm for me. I just wanted to add that if you don't need to resume the animation from where it left off, but simulate it kept running on the background, you can simply omit these lines:
let timeSincePause: CFTimeInterval = self.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
self.beginTime = timeSincePause
from the resume()
function in CALayer
extension, and leave beginTime
set to 0.0
.
Also, you can manually call willResignActive
and didBecomeActive
methods for animations you want to resume when ViewController
disappears, even if the app didn't go to the background.
Related videos on Youtube
Comments
-
Stultus almost 2 years
when my app comes out of the background the animation has stopped. which is normal. but i want to restart my animation from the current state. how do i do that without my snapping all over the place.
[UIView animateWithDuration:60 delay:0 options:(UIViewAnimationOptionCurveLinear |UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState) animations:^{ [bg setFrame:CGRectMake(0, 0, 1378, 1005)]; } completion:nil];
i tried putting a set frame in front of the animation but that just makes it snap.
[bg setFrame:CGRectMake(0, 0, 1378, 1005)];
any ideas?
-
danny_23 about 13 yearsHi. Could you explain a bit about what kind of effect you expect form that code? I think that "current state" is the current state of running (in-progress) animation. And I suppose that CA Framework doesn't care about remembering the state where the animation was interrupted. Are you trying to write a cycling animation that can be stopped and restarted again from where it was stopped without playback to initial position?
-
Stultus about 13 yearshi. it's a kind of ken burns effect.
-
Stultus about 13 yearsand the current state is the state the object is in at the moment. So it takes in account all the animation currently active on the object
-
-
Deepak Danduprolu about 13 years+1 Captures the basic idea. Although, in this case @Stultus needn't set up a delegate and code it in
animationDidStop:finished:
. He can implement thefinish:
block of hisanimateWithDuration:delay:options:animations:finished:
method. The block has afinished
argument which will tell him if the animation has been interrupted. Rest of it should be like you mentioned. -
Tricertops almost 10 years+1 Notifications are better way to receive this kind of event, than using application delegate.
-
Lee Fastenau over 9 years+1 This definitely is the more flexible and elegant solution for restarting looping animations. The UIApplicationDelegate shouldn't have to care about restarting MYWaitCycleView's spinning chicken animation. @Ramiro's code is perfectly happy sitting in a UIViewController or UIView subclass, close to the thing being animated.
-
Jonny over 7 yearsI've used this method for years, but now ran into an issue where animations still stop and aren't restarted (they should be running indefinitely in my case); it happens when tapping the alert asking the user for approval of remote (and/or local) push alerts. I've yet to find a solution. Btw I use the notification
UIApplicationDidBecomeActiveNotification
instead ofUIApplicationWillEnterForegroundNotification
. -
Lance Samaria about 5 yearsThis worked but what I had to do was remove the CALayer (I used a gradientLayer) that had the animation and then add it all over again. Other then that my animation would pause when coming back to the foreground. I explained it at the bottom of this answer stackoverflow.com/a/55883132/4833705
-
Jonatan Liljedahl over 3 yearsThis used to work well for me before, but at some point (maybe iOS 10?) I can't get the persisted animations to restore. Does anyone know what might have happened and if there's a solution for iOS 10 and later?
-
Honghao Zhang about 2 yearsCopied from developer.apple.com/library/archive/qa/qa1673/_index.html but didn't mention it. The method name is also misleading