Get the position of the path in time

Is there a good way to calculate the position of a path (CGPath or UIBezierPath) at a given point in time (from 0 to 1)?

Using CAShapeLayer, for example, you can create an animated stroke end. I want to know the position of this end of the stroke at arbitrary points in time.

Thanks in advance, Adrian

+5
objective-c core-graphics core-animation 2d cashapelayer
source share
3 answers

You can definitely base your approach on CADisplayLink and the tracking level. However, if you don't mind doing a little math yourself, the solution is not too complicated. In addition, you do not have to depend on the settings for the image link and additional layers. In fact, you don’t even need to depend on QuartzCore.

The following steps will work for any CGPathRef. In the case of UIBezierPath, select the CGPath property of the same:

  • Use CGPathApply along the path you want introspectively, as well as to the custom function CGPathApplierFunction .
  • Your CGPathApplierFunction will be called for each component of this path. CGPathElement (an argument for the application) will tell you which path element it is associated with the points that make this element (control points or end points).
  • You will be provided with one, two, three and four points for kCGPathElementMoveToPoint , kCGPathElementAddLineToPoint , kCGPathElementAddQuadCurveToPoint and kCGPathElementAddCurveToPoint respectively.
  • Keep these points within you in the representation of your choice. You only need to use CGPathApply once for each path, and this step is very quick.

Now, in math:

  • Depending on the time when you want to find a position in, say t , you will get an element (more on this later) and its component points.
  • If the element type is kCGPathElementMoveToPoint , its linear interpolation is p0 + t * (p1 - p0) (for x and y)
  • If the element type is kCGPathElementAddQuadCurveToPoint , its quadratic ((1 - t) * (1 - t)) * p0 + 2 * (1 - t) * t * p1 + t * t * p2
  • If the element type is kCGPathElementAddCurveToPoint , its cubic bezier ((1 - t) * (1 - t) * (1 - t)) * p0 + 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t * p3

Now the question remains, how do you determine the path element at time t . You can assume that each element of the path gets an equal time slice or you can calculate the distance of each element and the score for fractional time (the previous approach works fine for me). Also, be sure to add time for all previous path elements (you don't need to look for interpolations for them).

As I said, this is just for completeness (and most likely, as Apple finds out this material on its own), and only if you are ready to do the math.

+4
source share

Based on the response to the display of Matt links, you can track the position of the endpoint by creating a second “invisible” keyframe animation.

NOTES:

  • With this technique, you do not need to calculate the position of the endpoint yourself.
  • You can use any form of path.
  • it was written in the controller class view of the basic iOS Single View application template in Xcode

Let's start with 3 properties:

 @interface ViewController () @property (nonatomic, strong) CADisplayLink *displayLink; @property (nonatomic, strong) CAShapeLayer *pathLayer; @property (nonatomic, strong) CALayer *trackingLayer; @end 

displayLink will allow us to run the code every time we refresh the screen. pathLayer provides visual effects that we will animate. trackingLayer provides an invisible layer that we will use to track the position of the strokeEnd animation on pathLayer .

We open our view controller as follows:

 @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self createDisplayLink]; [self createPathLayer]; [self createTrackingLayer]; [self startAnimating]; } ... 

Using the following methods ...

First, create a link to display and add it to the run loop (according to Matt's code):

 -(void)createDisplayLink { _displayLink = [CADisplayLink displayLinkWithTarget:self selector: @selector(displayLinkDidUpdate:)]; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; } 

Then we create a visible layer:

 -(void)createPathLayer { //create and style the path layer //add it to the root layer of the view controller 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]; } 

Then we create an “invisible” layer (that is, through a frame without dimensions) 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 coordinate system [_pathLayer addSublayer:_trackingLayer]; } 

Then we create a method that captures 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 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 method is very useful if you have paths that you want to follow but don’t want to be bothered by doing the math itself.

Some of the important reasons for this are:

  • different / user paths can be used (not just ellipses ...)
  • various multimedia synchronization functions can be used (you do not need to independently determine the math for convenience, because of or linear, etc.).
  • you can start / stop the animation of the tracking layer at any time (i.e. do not run continuously)
  • you can start / stop the image link at any time
  • Converting between different coordinates of a layer is quite simple, so you can have a layer inside layers inside layers and transfer their coordinates to any other layer.

EDIT Here's a link to the github repository: https://github.com/C4Code/layerTrackPosition

Here is a picture of my simulator:

tracking the position of a calayer that is being animated

+2
source share

If you keep records of all your points from the very beginning, you can calculate the distance between them.
When you want to know at a certain moment which of these points is animated on the screen, you can do this:

  • first, get the current value of strokeEnd (0 to 1) as follows:

    CAShapeLayer *presentationLayer = (CAShapeLayer*)[_pathLayer presentationLayer];

    CGFloat strokeValue = [[presentationLayer valueForKey:@"strokeEnd"] floatValue];

  • then calculate the distance traveled:

    CGFloat doneDistance = _allTheDistance * strokeValue;

  • after that you need to repeat all your points and calculate the distance between them until you get it doneDistance

This will not tell you exactly where the screen is, but the current point that is being animated. Maybe this will help someone.

0
source share

All Articles