Cashapelayer Animating Path Glitches/Flickers (From Ellipse to Rect and Back)

CAShapeLayer Animating Path Glitches / Flickers (From Ellipse to Rect and back)

Unfortunately this is a limitation of the otherwise awesome animatable path property of CAShapeLayers.

Basically it tries to interpolate between the two paths. It hits trouble when the destination path and start path have a different number of control points - and curves and straight edges will have this problem.

You can try to minimise the effect by drawing your ellipse as 4 curves instead of a single ellipse, but it still isn't quite right. I haven't found a way to go smoothly from curves to polygons.

You may be able to get most of the way there, then transfer to a fade animation for the last part - this won't look as nice, though.

CAShapeLayer, animating path with Transactions

The answer is simple but a bit unsatisfactory: while the path property is animatable, it doesn't support implicit animations. This is called out in the Discussion section of the documentation for the path property:

Unlike most animatable properties, path (as with all CGPathRef animatable properties) does not support implicit animation.

Explicit vs. Implicit animations

An "explicit" animation is an animation object (e.g. CABasicAnimation) that is explicitly added to the layer by calling -addAnimation:forKey: on the layer.

An "implicit" animation is an animation that happens implicitly as a result of changing an animatable property.

The animation is considered implicit even if the property is changed within a transaction.

CAShapeLayer Animation without having to update the path

You can simplify your code and omit the spring effect:

[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
[CATransaction setAnimationTimingFunction: kCAMediaTimingFunctionEaseInEaseOut];
layer.strokeEnd = 0.0;
[CATransaction commit];

The transaction will create and add an implicit animation object for you.

Animating between two bezier path shapes

The first important point is to construct the two bezier paths similarly, so the rectangle is a (trivial) analogue to the more complex shape.

// the complex bezier path
let initialPoint = CGPoint(x: 0, y: 0)
let curveStart = CGPoint(x: 0, y: (rect.size.height) * (0.2))
let curveControl = CGPoint(x: (rect.size.width) * (0.6), y: (rect.size.height) * (0.5))
let curveEnd = CGPoint(x: 0, y: (rect.size.height) * (0.8))
let firstCorner = CGPoint(x: 0, y: rect.size.height)
let secondCorner = CGPoint(x: rect.size.width, y: rect.size.height)
let thirdCorner = CGPoint(x: rect.size.width, y: 0)

var myBezierArc = UIBezierPath()
myBezierArc.moveToPoint(initialPoint)
myBezierArc.addLineToPoint(curveStart)
myBezierArc.addQuadCurveToPoint(curveEnd, controlPoint: curveControl)
myBezierArc.addLineToPoint(firstCorner)
myBezierArc.addLineToPoint(secondCorner)
myBezierArc.addLineToPoint(thirdCorner)

The simpler 'trivial' bezier path, that creates a rectangle, is exactly the same but the controlPoint is set so that it appears to not be there:

let curveControl = CGPoint(x: 0, y: (rect.size.height) * (0.5))

( Try removing the addQuadCurveToPoint line to get a very strange animation! )

And finally, the animation commands:

let myAnimation = CABasicAnimation(keyPath: "path")

if (isArcVisible == true) {
myAnimation.fromValue = myBezierArc.CGPath
myAnimation.toValue = myBezierTrivial.CGPath
} else {
myAnimation.fromValue = myBezierTrivial.CGPath
myAnimation.toValue = myBezierArc.CGPath
}
myAnimation.duration = 0.4
myAnimation.fillMode = kCAFillModeForwards
myAnimation.removedOnCompletion = false

myImageView.layer.mask.addAnimation(myAnimation, forKey: "animatePath")

If anyone is interested, the project is here.

Animating a CGMutablePathRef with CAShapeLayer

You can animate the path of the shape layer from one path to another.

In your case you would create the paths for the initial state and for the end state as two different paths.

Note: if your two paths have a different number of segments or control points, the animation will look very strange.

Then you would create an animation for the "path" key path with those to and from values and add it to the layer you are animating. If you also want the property to change to the end state you should also update the path property to reflect the end state of the animation.

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"];
animation.duration = 1.0; // however long your duration should be
animation.fromValue = (__bridge id)fromPath;
animation.toValue = (__bridge id)toPath;

shapeLayer.path = toPath; // the property should reflect the end state of the animation
[shapeLayer addAnimation:animation forKey:@"myPathAnimation"];

CoreAnimation CAShapeLayer Animating Bezier Path

You can take current animated value using presentationLayer

CGFloat val = [[bezier.presentationLayer valueForKey:@"strokeEnd"] floatValue];

Animate line segment in iOS

You can use a CAShapeLayer and create a CGPath containing only two control points. The path property of CAShapeLayer itself actually is animatable (as long as the new path has the same number of points) plus you get all the transform capabilities of CALayer. And as Tommy just mentioned, you can play with strokeStart and strokeEnd for some pretty cool animations (there also is lineDashPatternwhich animates nicely with lineDashPhasebut i guess you won't need that).

Code Sample from this question:

CAShapeLayer *lineShape = nil;
CGMutablePathRef linePath = nil;
linePath = CGPathCreateMutable();
lineShape = [CAShapeLayer layer];

lineShape.lineWidth = 1.0f;
lineShape.lineCap = kCALineJoinMiter;
lineShape.strokeColor = [[UIColor redColor] CGColor];

CGPathMoveToPoint(linePath, NULL, x, y);
CGPathAddLineToPoint(linePath, NULL, toX, toY);

lineShape.path = linePath;
CGPathRelease(linePath);


Related Topics



Leave a reply



Submit