How to get coordinates of view during animation? - ios

I've created CAShapeLayer and added Bezier Cubic path(with addCurveToPoint:controlPoint1:controlPoint2: function) and added a little view on start point of that curve. After that I've created CAKeyFrameAnimation and moved that little view from start point of curve to the end point with animation. I want to get the center point of that view during animation. Any ideas?
CGPoint start, p, p1,p2;
start = CGPointMake(0, 150);
p = CGPointMake(300, 150);
p1 = CGPointMake(50, 225);
p2 = CGPointMake(250, 75);
[self.bezierPath moveToPoint:start];
[self.bezierPath addCurveToPoint:p
controlPoint1:p1
controlPoint2:p2];
NSLog(#"self bezierpath = %#",self.bezierPath);
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = self.bezierPath.CGPath;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.lineWidth = 3.f;
[self.yellowView.layer addSublayer:shapeLayer];
self.sliderThumb = [[UIView alloc]initWithFrame:(CGRect){0,0,20,20}];
self.sliderThumb.backgroundColor = [UIColor blackColor];
self.sliderThumb.center = CGPointMake(0, 150);
[self.yellowView addSubview:self.sliderThumb];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = #"position";
animation.duration = 4.f;
animation.path = self.bezierPath.CGPath;
[self.sliderThumb.layer addAnimation:animation forKey:nil];
CALayer *layer = self.sliderThumb.layer.presentationLayer;
NSLog(#"layer coordinates %f", layer.frame.origin.x);
`

The presentationLayer of the view's layer is a CALayer which captures where the layer is mid-animation.
For example, if you want to get updates frame by frame, set up a CADisplayLink and examine the presentationLayer in the display link's handler:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.duration = 4.0f;
animation.path = self.bezierPath.CGPath;
animation.delegate = self; // so we know when animation finished
[self startDisplayLink]; // start display link
[self.sliderThumb.layer addAnimation:animation forKey:nil];
With:
#property (nonatomic, weak) CADisplayLink *displayLink;
And:
// Called when animation stops (assuming you set the `delegate` for the animation).
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
[self stopDisplayLink:self.displayLink];
}
/// Start the display link.
- (void)startDisplayLink {
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(handleDisplayLink:)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
self.displayLink = displayLink;
}
/// Stop the display link.
///
/// #parameter displayLink The display link to be stopped.
- (void)stopDisplayLink:(CADisplayLink *)displayLink {
[displayLink invalidate];
}
/// The display link handler.
///
/// Called once for each frame of the animation.
///
/// #parameter displayLink The display link being handled.
- (void)handleDisplayLink:(CADisplayLink *)displayLink {
CALayer *layer = self.sliderThumb.layer.presentationLayer;
NSLog(#"layer coordinates %f", layer.frame.origin.x);
}

Related

Circular Progress Bar with CAShapeLayer and CABasicAnimation

I am trying to make Circular Progress View using the idea from here
This is my code for the CircleView:
#import "CircleView.h"
#interface CircleView()
#property (nonatomic, strong) CAShapeLayer *circleLayer;
#end
#implementation CircleView
-(instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = UIColor.clearColor;
UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width / 2., self.frame.size.height / 2.0) radius: (self.frame.size.width - 10) / 2 startAngle:0.0 endAngle:M_PI * 2 clockwise:YES];
CAShapeLayer *downLayer = [[CAShapeLayer alloc] init];
downLayer.path = circlePath.CGPath;
downLayer.fillColor = UIColor.clearColor.CGColor;
downLayer.strokeColor = UIColor.lightGrayColor.CGColor;
downLayer.lineWidth = 10.0;
downLayer.strokeEnd = 1.0;
self.circleLayer = [[CAShapeLayer alloc] init];
self.circleLayer.path = circlePath.CGPath;
self.circleLayer.fillColor = UIColor.clearColor.CGColor;
self.circleLayer.strokeColor = UIColor.redColor.CGColor;
self.circleLayer.lineWidth = 10.0;
self.circleLayer.strokeEnd = 0.0;
[self.layer addSublayer:downLayer];
[self.layer addSublayer: self.circleLayer];
}
return self;
}
-(void)animateCircle:(NSTimeInterval)duration fromPart:(CGFloat)fromPart toPart: (CGFloat)toPart {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration = duration;
animation.fromValue = #(fromPart);
animation.toValue = #(toPart);
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
self.circleLayer.strokeEnd = toPart;
[self.circleLayer addAnimation:animation forKey:#"animateCircle"];
}
#end
The problem is when I try to make this animation more than once. Then it shows only the animation from the last call and as if the previous calls have already finished animating. For example if I make the following two calls:
[self.circleView animateCircle:1 fromPart:0.0 toPart:0.25];
[self.circleView animateCircle:1 fromPart:0.25 toPart:0.5];
It will show only animating the colouring from 0.25 percentage of the circle to 0.5 percentage of the circle. Can I change that? I need to show all animations one after another. (Need to show the progress of a download)
You need to remove these 2 below lines and it will work as expected.
animation.fromValue = #(fromPart);
animation.toValue = #(toPart);
Because you use CircleView to display progress of a download so your method don't need fromPart value when animating circle. You can update animateCircle method like below code.
-(void)animateCircle:(NSTimeInterval)duration toPart: (CGFloat)toPart {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration = duration;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
self.circleLayer.strokeEnd = toPart;
[self.circleLayer addAnimation:animation forKey:#"animateCircle"];
}
Result

Animate path of shape layer

I'm stumped by what I thought would be a simple problem.
I'd like to draw views connected by lines, animate the position of the views and have the connecting line animate too. I create the views, and create a line between them like this:
- (UIBezierPath *)pathFrom:(CGPoint)pointA to:(CGPoint)pointB {
CGFloat halfY = pointA.y + 0.5*(pointB.y - pointA.y);
UIBezierPath *linePath=[UIBezierPath bezierPath];
[linePath moveToPoint: pointA];
[linePath addLineToPoint:CGPointMake(pointA.x, halfY)];
[linePath addLineToPoint:CGPointMake(pointB.x, halfY)];
[linePath addLineToPoint:pointB];
return linePath;
}
-(void)makeTheLine {
CGPoint pointA = self.viewA.center;
CGPoint pointB = self.viewB.center;
CAShapeLayer *lineShape = [CAShapeLayer layer];
UIBezierPath *linePath=[self pathFrom:pointA to:pointB];
lineShape.path=linePath.CGPath;
lineShape.fillColor = nil;
lineShape.opacity = 1.0;
lineShape.strokeColor = [UIColor blackColor].CGColor;
[self.view.layer addSublayer:lineShape];
self.lineShape = lineShape;
}
It draws just how I want it to. My understanding from the docs is that I am allowed to animate a shape's path by altering it in an animation block, like this:
- (void)moveViewATo:(CGPoint)dest {
UIBezierPath *destPath=[self pathFrom:dest to:self.viewB.center];
[UIView animateWithDuration:1 animations:^{
self.viewA.center = dest;
self.lineShape.path = destPath.CGPath;
}];
}
But no dice. The view position animates as expected, but the line connecting to the other view "jumps" right away to the target path.
This answer implies that what I'm doing should work. And this answer suggests a CABasic animation, which seems worse to me since (a) I'd then need to coordinate with the much cooler block animation done to the view, and (b) when I tried it this way, the line didn't change at all....
// worse way
- (void)moveViewATo:(CGPoint)dest {
UIBezierPath *linePath=[self pathFrom:dest to:self.viewB.center];
[UIView animateWithDuration:1 animations:^{
self.viewA.center = dest;
//self.lineShape.path = linePath.CGPath;
}];
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:#"path"];
morph.duration = 1;
morph.toValue = (id)linePath.CGPath;
[self.view.layer addAnimation:morph forKey:nil];
}
Thanks in advance.
Thanks all for the help. What I discovered subsequent to asking this is that I was animating the wrong property. It turns out, you can replace the layer's shape in an animation, like this:
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:#"path"];
morph.duration = 1;
morph.fromValue = (__bridge id)oldPath.path;
morph.toValue = (__bridge id)newPath.CGPath;
[line addAnimation:morph forKey:#"change line path"];
line.path=linePath.CGPath;
I guess this is all you need:
#import "ViewController.h"
#interface ViewController ()
//the view to animate, nothing but a simple empty UIView here.
#property (nonatomic, strong) IBOutlet UIView *targetView;
#property (nonatomic, strong) CAShapeLayer *shapeLayer;
#property NSTimeInterval animationDuration;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//the shape layer appearance
self.shapeLayer = [[CAShapeLayer alloc]init];
self.shapeLayer.strokeColor = [UIColor blackColor].CGColor;
self.shapeLayer.fillColor = [UIColor clearColor].CGColor;
self.shapeLayer.opacity = 1.0;
self.shapeLayer.lineWidth = 2.0;
[self.view.layer insertSublayer:self.shapeLayer below:self.targetView.layer];
//animation config
self.animationDuration = 2;
}
- (UIBezierPath *)pathFrom:(CGPoint)pointA to:(CGPoint)pointB {
CGFloat halfY = pointA.y + 0.5*(pointB.y - pointA.y);
UIBezierPath *linePath=[UIBezierPath bezierPath];
[linePath moveToPoint: pointA];
[linePath addLineToPoint:CGPointMake(pointA.x, halfY)];
[linePath addLineToPoint:CGPointMake(pointB.x, halfY)];
[linePath addLineToPoint:pointB];
return linePath;
}
- (void) moveViewTo: (CGPoint) point {
UIBezierPath *linePath= [self pathFrom:self.targetView.center to:point];
self.shapeLayer.path = linePath.CGPath;
//Use CAKeyframeAnimation to animate the view along the path
//animate the position of targetView.layer instead of the center of targetView
CAKeyframeAnimation *viewMovingAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
viewMovingAnimation.duration = self.animationDuration;
viewMovingAnimation.path = linePath.CGPath;
//set the calculationMode to kCAAnimationPaced to make the movement in a constant speed
viewMovingAnimation.calculationMode =kCAAnimationPaced;
[self.targetView.layer addAnimation:viewMovingAnimation forKey:viewMovingAnimation.keyPath];
//draw the path, animate the keyPath "strokeEnd"
CABasicAnimation *lineDrawingAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
lineDrawingAnimation.duration = self.animationDuration;
lineDrawingAnimation.fromValue = [NSNumber numberWithDouble: 0];
lineDrawingAnimation.toValue = [NSNumber numberWithDouble: 1];
[self.shapeLayer addAnimation:lineDrawingAnimation forKey:lineDrawingAnimation.keyPath];
//This part is crucial, update the values, otherwise it will back to its original state
self.shapeLayer.strokeEnd = 1.0;
self.targetView.center = point;
}
//the IBAction for a UITapGestureRecognizer
- (IBAction) viewDidTapped:(id)sender {
//move the view to the tapped location
[self moveViewTo:[sender locationInView: self.view]];
}
#end
Some explanation:
For UIViewAnimation, the property value is changed when the
animation is completed. For CALayerAnimation, the property value is
never change, it is just an animation and when the animation is
completed, the layer will go to its original state (in this case, the
path).
Putting self.lineShape.path = linePath.CGPath doesn't work is
because self.linePath is a CALayer instead of a UIView, you
have to use CALayerAnimation to animate a CALayer
To draw a path, it's better to animate the path drawing with keyPath
strokeEnd instead of path. I'm not sure why path worked in the
original post, but it seems weird to me.
CAKeyframeAnimation (instead of CABasicAnimation or UIViewAnimation) is used to animate the view along the path. (I guess you would prefer this to the linear animation directly from start point to end point). Setting calculationMode to kCAAnimationPaced will give a constant speed to the animation, otherwise the view moving will not sync with the line drawing.

Draw a circle on a Long Press event

I am drawing a circle on the screen as the user taps on a button. The animation duration is already set and also the from and to value are also set.
What I want to achieve is that somehow the animation should commence as the user long presses the button and continues till he is maintaining the tap on the screen i.e for the duration of Long Press.
As soon as the user lifts his finger the circle should stop to the point to where it has been completed till now.
Here is my code:
-(void)startCircularAnimation{
int radius = 50;
CAShapeLayer *circle = [CAShapeLayer layer];
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Center the shape in self.view
circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor redColor].CGColor;
circle.lineWidth = 5;
// Add to parent layer
[self.view.layer addSublayer:circle];
// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 15.0;
drawAnimation.repeatCount = 1.0; // Animate only once..
// Animate from no part of the stroke being drawn to the entire stroke being drawn
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:counter/drawAnimation.duration];
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
// Add the animation to the circle
[circle addAnimation:drawAnimation forKey:#"draw"];
}
This method performs animation and the from value is calculated from a timer which I started on the touch began case of the long press handler method. I am not able to get the perfect duration for the long press.
The long press event methods is something like this.
- (void)_handleLongPressGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer{
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
{
counter = 0;
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(incrementCounter) userInfo:nil repeats:YES];
}
case UIGestureRecognizerStateEnded:{
NSLog(#"State ended");
[timer invalidate];
break;
}
case UIGestureRecognizerStateCancelled:{
NSLog(#"State cancelled");
break;
}
case UIGestureRecognizerStateFailed:
{
break;
}
default:
break;
}
}
and the increment counter method is as follows
- (void)incrementCounter {
counter++;
[self startCircularAnimation];
}
This is not giving me the desired effect for drawing the circle till the user has his finger on the screen.
Please suggest something in the code to get the desired functionality.
Thanks in advance.
You'll want to follow apples guidelines https://developer.apple.com/library/ios/qa/qa1673/_index.html
So in your interface i'd declare the following
#interface ViewController ()
#property (nonatomic, strong) CAShapeLayer *circle;
#property (nonatomic, strong) CABasicAnimation *drawAnimation;
#property (strong, nonatomic) IBOutlet UIButton *circleButton;
#end
Then in view did load
- (void)viewDidLoad
{
[super viewDidLoad];
int radius = 50;
self.circle = [CAShapeLayer layer];
// Make a circular shape
self.circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Center the shape in self.view
self.circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
// Configure the apperence of the circle
self.circle.fillColor = [UIColor clearColor].CGColor;
self.circle.strokeColor = [UIColor redColor].CGColor;
self.circle.lineWidth = 5;
self.circle.strokeEnd = 0.0f;
// Add to parent layer
[self.view.layer addSublayer:_circle];
// Target for touch down (hold down)
[self.circleButton addTarget:self action:#selector(startCircleAnimation) forControlEvents:UIControlEventTouchDown];
// Target for release
[self.circleButton addTarget:self action:#selector(endCircleAnimation) forControlEvents:UIControlEventTouchUpInside];
/**
Don't start Animation in viewDidLoad to achive the desired effect
*/
}
Function to start the animation and resume it (probably needs a better name)
-(void)startCircleAnimation{
if (_drawAnimation) {
[self resumeLayer:_circle];
} else {
[self circleAnimation];
}
}
Function to end animation
-(void)endCircleAnimation{
[self pauseLayer:_circle];
}
Function to generate animation
- (void)circleAnimation
{
// Configure animation
self.drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
self.drawAnimation.duration = 10.0;
self.drawAnimation.repeatCount = 1.0; // Animate only once..
// Animate from no part of the stroke being drawn to the entire stroke being drawn
self.drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
// Set your to value to one to complete animation
self.drawAnimation.toValue = [NSNumber numberWithFloat:1.0f];
// Experiment with timing to get the appearence to look the way you want
self.drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
// Add the animation to the circle
[self.circle addAnimation:_drawAnimation forKey:#"draw"];
}
Pause and stop functions from apple
- (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;
}
The main point you'd want to take from this is that your not keeping account of how long the button is pressed for your just keeping account of the events that are sent from the button UIControlEventTouchDown and UIControlEventTouchUpInside
Edit:Gif
I think you should make your from value related to value of counter, this will make the drawing start from what it was left behind last time the user lift his finger.
You should also make your timer's time interval smaller, 1 second is too long, 0.1 second will be better.

Why when I set a low duration value for my CABasicAnimation does it jump?

Sample Project: http://cl.ly/1W3V3b0D2001
I'm using CABasicAnimation to create a progress indicator that is like a pie chart. Similar to the iOS 7 app download animation:
The animation is set up as follows:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGFloat radius = CGRectGetWidth(self.frame) / 2;
CGFloat inset = 1;
CAShapeLayer *ring = [CAShapeLayer layer];
ring.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
cornerRadius:radius-inset].CGPath;
ring.fillColor = [UIColor clearColor].CGColor;
ring.strokeColor = [UIColor whiteColor].CGColor;
ring.lineWidth = 2;
self.innerPie = [CAShapeLayer layer];
inset = radius/2;
self.innerPie.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
cornerRadius:radius-inset].CGPath;
self.innerPie.fillColor = [UIColor clearColor].CGColor;
self.innerPie.strokeColor = [UIColor whiteColor].CGColor;
self.innerPie.lineWidth = (radius-inset)*2;
self.innerPie.strokeStart = 0;
self.innerPie.strokeEnd = 0;
[self.layer addSublayer:ring];
[self.layer addSublayer:self.innerPie];
self.progress = 0.0;
}
The animation is triggered by setting the progress of the view:
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated {
self.progress = progress;
if (animated) {
CGFloat totalDurationForFullCircleAnimation = 0.25;
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
self.innerPie.strokeEnd = progress;
pathAnimation.delegate = self;
pathAnimation.fromValue = #([self.innerPie.presentationLayer strokeEnd]);
pathAnimation.toValue = #(progress);
pathAnimation.duration = totalDurationForFullCircleAnimation * ([pathAnimation.toValue floatValue] - [pathAnimation.fromValue floatValue]);
[self.innerPie addAnimation:pathAnimation forKey:#"strokeEndAnimation"];
}
else {
[CATransaction setDisableActions:YES];
[CATransaction begin];
self.innerPie.strokeEnd = progress;
[CATransaction commit];
}
}
However, in cases where I set the progress to something small, such as 0.25, there's a jump in the animation. It goes a little forward clockwise, jumps back, then keeps going forward as normal. It's worth nothing that this does not happen if the duration or progress is set higher.
How do I stop the jump? This code works well in every case except when the progress is very low. What am I doing wrong?
Ah! We should have seen this earlier. The problem is this line in your if (animated) block:
self.innerPie.strokeEnd = progress;
Since innerPie is a Core Animation layer, this causes an implicit animation (most property changes do). This animation is fighting with your own animation. You can prevent this from happening by disabling implicit animation while setting the strokeEnd:
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.innerPie.strokeEnd = progress;
[CATransaction commit];
(Note how setDisableActions: is within the begin/commit.)
Alternatively, you can remove your own animation and just use the automatic one, using something like setAnimationDuration: to change its length.
Original Suggestions:
My guess is that your drawRect: is being called, which resets your strokeEnd value. Those layers should probably not be set up in drawRect: anyway. Try moving that setup to an init method or didMoveToWindow: or similar.
If that's not effective, I would suggest adding log statements to track the value of progress and [self.innerPie.presentationLayer strokeEnd] each time the method is called; perhaps they're not doing what you expect.
Why are you using drawRect:? There should be no drawRect: method in your class.
You should not be using drawRect if you are also using self.layer. Use one or the other, never both.
Change it to something like:
- (id)initWithFrame:(CGRect)frame
{
if (!(self = [super initWithFrame:frame])
return nil;
CGFloat radius = CGRectGetWidth(self.frame) / 2;
CGFloat inset = 1;
CAShapeLayer *ring = [CAShapeLayer layer];
ring.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
cornerRadius:radius-inset].CGPath;
ring.fillColor = [UIColor clearColor].CGColor;
ring.strokeColor = [UIColor whiteColor].CGColor;
ring.lineWidth = 2;
self.innerPie = [CAShapeLayer layer];
inset = radius/2;
self.innerPie.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
cornerRadius:radius-inset].CGPath;
self.innerPie.fillColor = [UIColor clearColor].CGColor;
self.innerPie.strokeColor = [UIColor whiteColor].CGColor;
self.innerPie.lineWidth = (radius-inset)*2;
self.innerPie.strokeStart = 0;
self.innerPie.strokeEnd = 0;
[self.layer addSublayer:ring];
[self.layer addSublayer:self.innerPie];
self.progress = 0.0;
return self;
}
When travelling within same distance, the smaller the duration, the higher the speed. When you set the duration too small, the speed will be very high, and that's why it looks like jumping.

Animate previously added CALayer animations

I'm working on a component that is supposed to render several images and animate their position while changing also the contents. I've been inspired by this post: http://ronnqvi.st/controlling-animation-timing/ and thought of using speed to control the animations.
My ideia is to add the CALayers with the animations to a top-level layer whose speed is 0 and then set this layer's speed to 1.0 when it's time to animate.
Here's the code for it:
...
#property (nonatomic,strong) CALayer *animationLayer;
...
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.animationLayer = [CALayer layer];
self.animationLayer.frame = frame;
self.animationLayer.speed = 0.0;
[self.layer addSublayer:self.animationLayer];
}
return self;
}
- (void)addLayerData:(LayerData*)layerData
{
CALayer *aLayer = [CALayer layer];
aLayer.contents = (id) layerData.image.CGImage;
aLayer.frame = CGRectMake(0,0,96,96);
aLayer.position = layerData.position;
aLayer.name = layerData.name;
CGRect pathLayerFrame = layerData.path.bounds;
pathLayerFrame.origin.x = 0.0;
pathLayerFrame.origin.y = 0.0;
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.frame = pathLayerFrame;
pathLayer.path = layerData.path.CGPath;
pathLayer.strokeColor = layerData.strokeColor.CGColor;
pathLayer.fillColor = [UIColor clearColor].CGColor;
pathLayer.lineWidth = 10;
pathLayer.lineCap = kCALineCapRound;
[self.animationLayer addSublayer:pathLayer];
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
[positionAnimation setPath: layerData.path.CGPath];
[positionAnimation setDuration: 10.0];
[positionAnimation setRemovedOnCompletion: NO];
[positionAnimation setFillMode: kCAFillModeBoth];
[positionAnimation setKeyTimes:layerData.keyTimes];
CAKeyframeAnimation *contentsAnimation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
[contentsAnimation setValues:[self buildContentImagesFromLayerData:layerData];
[contentsAnimation setCalculationMode:kCAAnimationDiscrete];
contentsAnimation.calculationMode = kCAAnimationLinear;
[contentsAnimation setDuration:0.1];
[contentsAnimation setDelegate:self];
[contentsAnimation setAutoreverses:YES];
[contentsAnimation setRepeatCount:HUGE_VALF];
[contentsAnimation setRemovedOnCompletion:NO];
[contentsAnimation setFillMode:kCAFillModeBoth];
[aLayer addAnimation:positionAnimation forKey:kPostionAnimationKey];
[aLayer addAnimation:contentsAnimation forKey:kContentsAnimationKey];
[self.animationLayer addSublayer:aLayer];
}
- (void)animate
{
self.animationLayer.speed = 1.0;
}
My issue is that although the animationLayer contains all the layers to animate and these have all the animations correctly defined I can't seem to get the layers moving around the screen.
The addLayerData is called several times by external client code, at different times (that is when user taps a button to add an image). The animate is called after that when the user taps a button.
Maybe I've missed something in the article.
Can anyone help me out on this one?
Thanks in advance.
UPDATE
If I drop the animationLayer and add the layers to be animated directly on the view's layer (self.layer) it works correctly.

Resources