I found it tricky to animate a UIImageView
between two states: its original rectangle frame, and a new shape created with a UIBezierPath
. There are many different techniques mentioned, most of which did not work for me.
First was the realization that using UIView block animation would not work; evidently one can't perform sublayer animations in an animateWithDuration:
block. (see here and here)
That left CAAnimation
, with the concrete subclasses like CABasicAnimation
. I soon realized that one can't animate from a view that doesn't have a CAShapeLayer
to one that does (see here, for example).
And they can't be just any two shape layer paths, but rather "Animating the path of a shape layer is only guaranteed to work when you are animating from like to like" (see here)
With that in place, comes the more mundane problems, like what to use for fromValue
and toValue
(should they be a CAShapeLayer
, or a CGPath
?), what to add the animation to (the layer
, or the mask
?), etc.
It seemed there were so many variables; which combination would give me the animation I was looking for?
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.