6

I have a UIView whose backing layer has a CAKeyframeAnimation with a simple straight line path set as its `path`.
Can I have the animation "frozen", so to speak, and manually change its progress?
For example: If the path is 100 points in length, setting the progress (offset?) to 0.45 should have the view move 45 points down the path.

I remember seeing an article that did something similar (moving a view along a path based on the value from a slider) via CAMediaTiming interfaces, but I haven't been able to find it, even after a few hours of searching. If I'm approaching this in a completely wrong way, please do let me know. Thanks.

Here's some sample code, if the above isn't clear enough.

- (void)setupAnimation
{

    CAKeyFrameAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:_label.layer.position];
    [path addLineToPoint:(CGPoint){200, 200}];
    
    animation.path = path.CGPath;
    
    animation.duration = 1;
    animation.autoreverses = NO;
    animation.removedOnCompletion = NO;
    animation.speed = 0;
    
    // _label is just a UILabel in a storyboard
    [_label.layer addAnimation:animation forKey:@"LabelPathAnimation"]; 
}

- (void)sliderDidSlide:(UISlider *)slider
{
    // move _label along _animation.path for a distance that corresponds to slider.value
}
Community
  • 1
  • 1
caughtinflux
  • 101
  • 1
  • 6

2 Answers2

4

This is based on what Jonathan said, only a bit more to the point. The animation is set up correctly, but the slider action method should be as follows:

- (void)sliderDidSlide:(UISlider *)slider 
{
    // Create and configure a new CAKeyframeAnimation instance
    CAKeyframeAnimation *animation = ...;
    animation.duration = 1.0;
    animation.speed = 0;
    animation.removedOnCompletion = NO;
    animation.timeOffset = slider.value;

    // Replace the current animation with a new one having the desired timeOffset
    [_label.layer addAnimation:animation forKey:@"LabelPathAnimation"];
}

This will make the label move along the animation's path based on timeOffset.

caughtinflux
  • 101
  • 1
  • 6
  • when the slider value reaches 1.0 the animation turns to it initial value. How do you deal with that? – MatterGoal Dec 14 '13 at 12:39
  • @MatterGoal you need to set the actual final value after the animation is attached. The animation isn't changing the actual value, it's only doing a visual representation of that value's change. – averydev Dec 18 '13 at 20:31
1

Yes you can do this with the CAMediaTiming interface. You can set the speed of the layer to 0 and manualy set the timeOffset. Example of a simple pause/resume method:

- (void)pauseAnimation {
    CFTimeInterval pausedTime = [yourLayer convertTime:CACurrentMediaTime() fromLayer:nil];
    yourLayer.speed = 0.0;
    yourLayer.timeOffset = pausedTime;
}

- (void)resumeAnimation {

    CFTimeInterval pausedTime = [yourLaye timeOffset];
    if (pausedTime != 0) {
        yourLayer.speed = 1.0;
        yourLayer.timeOffset = 0.0;
        yourLayer.beginTime = 0.0;

        CFTimeInterval timeSincePause = [yourLayer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
        yourLayer.beginTime = timeSincePause;
    }
}
Jonathan Cichon
  • 4,396
  • 16
  • 19
  • I assume you mean `yourAnimation.timeOffset`, etc.? – caughtinflux Jul 04 '13 at 09:52
  • the `CACurrentMediaTime` interface is implemented by both the layer (CALayer) and the animation (CAAnimation). If you use the layer, all animations currently added to the layer should be affected, if you use the animation, only the specific animation should be affected. – Jonathan Cichon Jul 04 '13 at 09:56
  • Right, sorry. I only just realised that, and was in the process of editing my comment. – caughtinflux Jul 04 '13 at 09:57
  • I wasn't looking for a pause/resume method (as shown by Apple [here](https://developer.apple.com/library/ios/#qa/qa2009/qa1673.html)). It's still not clear as to how I'd go about doing what I asked in the question. – caughtinflux Jul 04 '13 at 10:05
  • you can set the speed of your animation to `0` (so no automatic movement at all) and than set the timeOffset manually to a value between `0` (0 points) and `1` (100 points). You can try this with a UISLider as input for your timeOffset values. – Jonathan Cichon Jul 04 '13 at 10:25
  • [This](http://pastie.org/8109511) is what I'm doing right now, but the label just stays put. – caughtinflux Jul 04 '13 at 10:39
  • Changing the timeOffset only changed the position of the label if I set it _before_ adding the animation. Finally ended up adding/removing the animation every time I needed to change the offset. Thanks. – caughtinflux Jul 04 '13 at 12:14