I have an endlessly looping CABasicAnimation
of a repeating image tile in my view:
a = [CABasicAnimation animationWithKeyPath:@"position"];
a.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionLinear];
a.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
a.toValue = [NSValue valueWithCGPoint:CGPointMake(image.size.width, 0)];
a.repeatCount = HUGE_VALF;
a.duration = 15.0;
[a retain];
I have tried to "pause and resume" the layer animation as described in Technical Q&A QA1673.
When the app enters background, the animation gets removed from the layer.
To compensate I listen to UIApplicationDidEnterBackgroundNotification
and call stopAnimation
and in response to UIApplicationWillEnterForegroundNotification
call startAnimation
.
- (void)startAnimation
{
if ([[self.layer animationKeys] count] == 0)
[self.layer addAnimation:a forKey:@"position"];
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)stopAnimation
{
CFTimeInterval pausedTime =
[self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
self.layer.speed = 0.0;
self.layer.timeOffset = pausedTime;
}
The problem is that it starts again at the beginning and there is ugly jump from last position, as seen on app snapshot the system took when application did enter background, back to the start of the animation loop.
I can not figure out how to make it start at last position, when I re-add the animation. Frankly, I just don't understand how that code from QA1673 works: in resumeLayer
it sets the layer.beginTime twice, which seems redundant. But when I've removed the first set-to-zero, it did not resume the animation where it was paused. This was tested with simple tap gesture recognizer, that did toggle the animation - this is not strictly related to my issues with restoring from background.
What state should I remember before the animation gets removed and how do I restore the animation from that state, when I re-add it later?
Hey I had stumbled upon the same thing in my game, and ended up finding a somewhat different solution than you, which you may like :) I figured I should share the workaround I found...
My case is using UIView/UIImageView animations, but it's basically still CAAnimations at its core... The gist of my method is that I copy/store the current animation on a view, and then let Apple's pause/resume work still, but before resuming I add my animation back on. So let me present this simple example:
Let's say I have a UIView called movingView. The UIView's center is animated via the standard [UIView animateWithDuration...] call. Using the mentioned QA1673 code, it works great pausing/resuming (when not exiting the app)... but regardless, I soon realized that on exit, whether I pause or not, the animation was completely removed... and here I was in your position.
So with this example, here's what I did:
When the app exits to background, I do this:
animationViewPosition = [[movingView.layer animationForKey:@"position"] copy]; // I know position is the key in this case...
[self pauseLayer:movingView.layer]; // this is the Apple method from QA1673
Now in my UIApplicationWillEnterForegroundNotification handler:
if (animationViewPosition != nil)
{
[movingView.layer addAnimation:animationViewPosition forKey:@"position"]; // re-add the core animation to the view
[animationViewPosition release]; // since we 'copied' earlier
animationViewPosition = nil;
}
[self resumeLayer:movingView.layer]; // Apple's method, which will resume the animation at the position it was at when the app exited
And that's pretty much it! It has worked for me so far :)
You can easily extend it for more animations or views by just repeating those steps for each animation. It even works for pausing/resuming UIImageView animations, ie the standard [imageView startAnimating]. The layer animation key for that (by the way) is "contents".
Listing 1 Pause and Resume animations.
-(void)pauseLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
-(void)resumeLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}