Building on Matt's display link answer, you can track the position of the end point by creating a second "invisible" keyframe animation.
NOTES:
- with this technique you don't need to calculate the position of the end point yourself
- you can use any path shape.
- this was written in the view controller class of a basic iOS Single View Application template in Xcode
We start with 3 properties:
@interface ViewController ()
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, strong) CAShapeLayer *pathLayer;
@property (nonatomic, strong) CALayer *trackingLayer;
@end
The displayLink
will allow us to run code every time the screen updates.
The pathLayer
provides the visuals, the one that we'll animate.
The trackingLayer
provides an invisible layer that we'll use to track the position of the strokeEnd
animation on the pathLayer
.
We open our view controller like so:
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self createDisplayLink];
[self createPathLayer];
[self createTrackingLayer];
[self startAnimating];
}
...
With the following methods...
We first create the display link and add it to the run loop (as per Matt's code):
-(void)createDisplayLink {
_displayLink = [CADisplayLink
displayLinkWithTarget:self
selector:
@selector(displayLinkDidUpdate:)];
[_displayLink
addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSDefaultRunLoopMode];
}
We then create the visible layer:
-(void)createPathLayer {
//create and style the path layer
//add it to the root layer of the view controller's view
_pathLayer = [CAShapeLayer layer];
_pathLayer.bounds = CGRectMake(0,0,100,100);
_pathLayer.path = CGPathCreateWithEllipseInRect(_pathLayer.bounds, nil);
_pathLayer.fillColor = [UIColor clearColor].CGColor;
_pathLayer.lineWidth = 5;
_pathLayer.strokeColor = [UIColor blackColor].CGColor;
_pathLayer.position = self.view.center;
[self.view.layer addSublayer:_pathLayer];
}
We then create an "invisible" (i.e. via a frame with no dimensions) layer to track:
-(void)createTrackingLayer {
_trackingLayer = [CALayer layer];
//set the frame (NOT bounds) so that we can see the layer
//uncomment the following two lines to see the tracking layer
//_trackingLayer.frame = CGRectMake(0,0,5,5);
//_trackingLayer.backgroundColor = [UIColor redColor].CGColor;
//we add the blank layer to the PATH LAYER
//so that its coordinates are always in the path layer's coordinate system
[_pathLayer addSublayer:_trackingLayer];
}
We then create a method that grabs the position of the tracking layer:
- (void)displayLinkDidUpdate:(CADisplayLink *)sender {
//grab the presentation layer of the blank layer
CALayer *presentationLayer = [_trackingLayer presentationLayer];
//grab the position of the blank layer
//convert it to the main view's layer coordinate system
CGPoint position = [self.view.layer convertPoint:presentationLayer.position
fromLayer:_trackingLayer];
//print it out, or do something else with it
NSLog(@"%4.2f,%4.2f",position.x,position.y);
}
... and the startAnimating
method:
-(void)startAnimating {
//begin the animation transaction
[CATransaction begin];
//create the stroke animation
CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
//from 0
strokeEndAnimation.fromValue = @(0);
//to 1
strokeEndAnimation.toValue = @(1);
//1s animation
strokeEndAnimation.duration = 10.0f;
//repeat forever
strokeEndAnimation.repeatCount = HUGE_VAL;
//ease in / out
strokeEndAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
//apply to the pathLayer
[_pathLayer addAnimation:strokeEndAnimation forKey:@"strokeEndAnimation"];
//NOTE: we don't actually TRACK above animation, its there only for visual effect
//begin the follow path animation
CAKeyframeAnimation *followPathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
//set the path for the keyframe animation
followPathAnimation.path = _pathLayer.path;
//add an array of times that match the NUMBER of points in the path
//for custom paths, you'll need to know the number of points and calc this yourself
//for an ellipse there are 5 points exactly
followPathAnimation.keyTimes = @[@(0),@(0.25),@(0.5),@(0.75),@(1)];
//copy the timing function
followPathAnimation.timingFunction = strokeEndAnimation.timingFunction;
//copy the duration
followPathAnimation.duration = strokeEndAnimation.duration;
//copy the repeat count
followPathAnimation.repeatCount = strokeEndAnimation.repeatCount;
//add the animation to the layer
[_trackingLayer addAnimation:followPathAnimation forKey:@"postionAnimation"];
[CATransaction commit];
}
This technique is pretty useful if you have paths you want to follow, but don't want to be bothered doing the math yourself.
Some of the valuable reasons for this are:
- different/custom paths can be used (not just ellipses...)
- different media timing functions can be used (you don't have to figure out the math yourself for ease in, out, or linear, etc...)
- you can start / stop animating the tracking layer at any time (i.e. doesn't have to run continuously)
- you can start / stop the display link at any time
- converting between different layer coordinates is quite easy, so you can have layer inside layers inside layers and still transfer their coordinates to any other layer
EDIT
Here's a link to a github repo: https://github.com/C4Code/layerTrackPosition
Here's an image of my simulator:
path
property. It may be a simpler solution, though I'm not sure what you have in mind. – Ursal