Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slow down & accelerate animations with CoreAnimation

I am trying to apply a slow-motion effect to my application, pretty much like how you can slow down most graphical effects of Mac OS if you press Shift.

My application uses CoreAnimation, so I thought it should be no biggie: set speed to some slower value (like 0.1) and set it back to 1 once I'm done, and here I go.

It seems, unfortunately, that this is not the right way. The slowdown works great, however when I want to get back to normal speed, it resumes as if the speed was 1 the whole time. This basically means that if I held Shift for long enough, as soon as I release it, the animation instantly completes.

I found a Technical QA page explaining how to pause and resume an animation, but I can't seem to get it right if it's not about entirely pausing the animation. I'm definitely not very good at time warping.

What would be the right way to slow down then resume an animation with CoreAnimation?

Here's the useful code:

-(void)flagsChanged:(NSEvent *)theEvent
{
    CALayer* layer = self.layer;
    [CATransaction begin];
    CATransaction.disableActions = YES;
    layer.speed = (theEvent.modifierFlags & NSShiftKeyMask) ? 0.1 : 1;
    [CATransaction commit];
}
like image 680
zneak Avatar asked Oct 09 '22 16:10

zneak


1 Answers

Tricky problem...

I set up a test with a basic UIView. I initiate an animation of its layer from it's current point to a target point.

In order to slow down the core animation, I actually had to construct and replace a new animation (since you can't modify the existing animation).

It's especially important to figure out how far along the current animation has already proceeded. This is done by accessing beginTime, currentTime, calculating the elapsed time and then figuring out how long should be the duration of the new animation.

- (void)initiate {
    if(!initiated) {
        [CATransaction begin];
        [CATransaction disableActions];
        [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut]];
        CABasicAnimation *ba = [CABasicAnimation animationWithKeyPath:@"position"];
        ba.duration = 10.0f;
        ba.fromValue = [NSValue valueWithCGPoint:view.layer.position];
        ba.toValue = [NSValue valueWithCGPoint:CGPointMake(384, 512)];
        [view.layer addAnimation:ba forKey:@"animatePosition"];
        [CATransaction commit];
        initiated = YES;
    }
}

- (void)slow {
    [CATransaction begin];
    CABasicAnimation *old = (CABasicAnimation *)[view.layer animationForKey:@"animatePosition"];
    CABasicAnimation *ba = [CABasicAnimation animationWithKeyPath:@"position"];
    CFTimeInterval animationBegin = old.beginTime;
    CFTimeInterval currentTime = CACurrentMediaTime();
    CFTimeInterval elapsed = currentTime - animationBegin;
    ba.duration = [[old valueForKey:@"duration"] floatValue] - elapsed;
    ba.duration = [[old valueForKey:@"duration"] floatValue];
    ba.autoreverses = [[old valueForKey:@"autoreverses"] boolValue];
    ba.repeatCount = [[old valueForKey:@"repeatCount"] floatValue];
    ba.fromValue = [NSValue valueWithCGPoint:((CALayer *)[view.layer presentationLayer]).position];
    ba.toValue = [old valueForKey:@"toValue"];
    ba.speed = 0.1;
    [view.layer addAnimation:ba forKey:@"animatePosition"];
    [CATransaction commit];
}

- (void)normal {
    [CATransaction begin];
    CABasicAnimation *old = (CABasicAnimation *)[view.layer animationForKey:@"animatePosition"];
    CABasicAnimation *ba = [CABasicAnimation animationWithKeyPath:@"position"];
    CFTimeInterval animationBegin = old.beginTime;
    CFTimeInterval currentTime = CACurrentMediaTime();
    CFTimeInterval elapsed = currentTime - animationBegin;
    ba.duration = [[old valueForKey:@"duration"] floatValue] - elapsed;
    ba.autoreverses = [[old valueForKey:@"autoreverses"] boolValue];
    ba.repeatCount = [[old valueForKey:@"repeatCount"] floatValue];
    ba.fromValue = [NSValue valueWithCGPoint:((CALayer *)[view.layer presentationLayer]).position];
    ba.toValue = [old valueForKey:@"toValue"];
    ba.speed = 1;
    [view.layer addAnimation:ba forKey:@"animatePosition"];
    [CATransaction commit];
}

Note: The above code works only in 1 direction, not with animations that autoreverse...

like image 72
C4 - Travis Avatar answered Oct 12 '22 10:10

C4 - Travis