I try to change a circle into a square and vice versa and I am almost there. However it doesn't animates as expected. I would like all corners of a square to be animated/morphed at the same time but what I get is the following:
I use CAShapeLayer
and CABasicAnimation
in order to animate shape property.
Here is how I create the circle path:
- (UIBezierPath *)circlePathWithCenter:(CGPoint)center radius:(CGFloat)radius
{
UIBezierPath *circlePath = [UIBezierPath bezierPath];
[circlePath addArcWithCenter:center radius:radius startAngle:0 endAngle:M_PI/2 clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:M_PI/2 endAngle:M_PI clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:M_PI endAngle:3*M_PI/2 clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:3*M_PI/2 endAngle:M_PI clockwise:YES];
[circlePath closePath];
return circlePath;
}
Here is the square path:
- (UIBezierPath *)squarePathWithCenter:(CGPoint)center size:(CGFloat)size
{
CGFloat startX = center.x-size/2;
CGFloat startY = center.y-size/2;
UIBezierPath *squarePath = [UIBezierPath bezierPath];
[squarePath moveToPoint:CGPointMake(startX, startY)];
[squarePath addLineToPoint:CGPointMake(startX+size, startY)];
[squarePath addLineToPoint:CGPointMake(startX+size, startY+size)];
[squarePath addLineToPoint:CGPointMake(startX, startY+size)];
[squarePath closePath];
return squarePath;
}
I apply the circle path to one of my View's layers, set the fill etc. It draws perfectly. Then in my gestureRecognizer selector I create and run the following animation:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"];
animation.duration = 1;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = (__bridge id)(self.stateLayer.path);
animation.toValue = (__bridge id)(self.stopPath.CGPath);
self.stateLayer.path = self.stopPath.CGPath;
[self.stateLayer addAnimation:animation forKey:@"animatePath"];
As you can notice in the circlePathWithCenter:radius:
and squarePathWithCenter:size:
I follow the suggestion from here (to have the same number of segments and control points):
Smooth shape shift animation
The animation looks better then in the post from above but it is still not the one I want to achieve :(
I know that I can do it with simple CALayer
and then set appropriate level of cornerRadius
to make a circle out of square/rectangle and after that animate cornerRadius
property to change it from circle to square but I'm still extremaly curious if it is even possible to do it with CAShapeLayer
and path
animation.
Thanks in advance for help!
After playing with it for a little while in the playground I noticed that each arc adds two segments to the bezier path, not just one. Moreover, calling closePath
adds two more. So to keep the number of segments consistent, I ended up with adding fake segments to my square path. The code is in Swift, but I think it doesn't matter.
func circlePathWithCenter(center: CGPoint, radius: CGFloat) -> UIBezierPath {
let circlePath = UIBezierPath()
circlePath.addArcWithCenter(center, radius: radius, startAngle: -CGFloat(M_PI), endAngle: -CGFloat(M_PI/2), clockwise: true)
circlePath.addArcWithCenter(center, radius: radius, startAngle: -CGFloat(M_PI/2), endAngle: 0, clockwise: true)
circlePath.addArcWithCenter(center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI/2), clockwise: true)
circlePath.addArcWithCenter(center, radius: radius, startAngle: CGFloat(M_PI/2), endAngle: CGFloat(M_PI), clockwise: true)
circlePath.closePath()
return circlePath
}
func squarePathWithCenter(center: CGPoint, side: CGFloat) -> UIBezierPath {
let squarePath = UIBezierPath()
let startX = center.x - side / 2
let startY = center.y - side / 2
squarePath.moveToPoint(CGPoint(x: startX, y: startY))
squarePath.addLineToPoint(squarePath.currentPoint)
squarePath.addLineToPoint(CGPoint(x: startX + side, y: startY))
squarePath.addLineToPoint(squarePath.currentPoint)
squarePath.addLineToPoint(CGPoint(x: startX + side, y: startY + side))
squarePath.addLineToPoint(squarePath.currentPoint)
squarePath.addLineToPoint(CGPoint(x: startX, y: startY + side))
squarePath.addLineToPoint(squarePath.currentPoint)
squarePath.closePath()
return squarePath
}