Introduction
I was curious about creating animations in UIKit. I wanted to animate different properties such as color
and path
.
Here is what I found:
It’s impossible to create complex animations only by using the block-based animation API. To do that, you need the Core Animation
API and CAPropertyAnimation
with its various subclasses.
Complex animation in UIKit is based on a few key components:
CAShapeLayer
- provides extensive customization options:path
,stroke
,fill
,shadow
CABasicAnimation
- helps animatecolor
or change thepath
Implementation
First Step
The first step is to create a shape layer that will draw an arrow using CAShapeLayer
.
private lazy var arrowShapeLayer: CAShapeLayer = {
let arrowShapeLayer = CAShapeLayer()
arrowShapeLayer.strokeColor = direction.arrowColour.cgColor
arrowShapeLayer.lineWidth = ArrowView.arrowLineWidth
arrowShapeLayer.lineCap = .round
arrowShapeLayer.fillColor = UIColor.clear.cgColor
return arrowShapeLayer
}()
Second Step
The second step is to animate the arrow direction by changing the shape layer’s path and stroke color.
var direction: Direction = .up {
didSet {
guard oldValue != direction else { return }
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.fromValue = arrowShapeLayer.presentation()?.path
pathAnimation.duration = 0.5
arrowShapeLayer.add(pathAnimation, forKey: "pathAnimation")
let strokeColourAnimation = CABasicAnimation(keyPath: "strokeColor")
strokeColourAnimation.fromValue = arrowShapeLayer.presentation()?.strokeColor
strokeColourAnimation.duration = 0.5
arrowShapeLayer.add(strokeColourAnimation, forKey: "strokeColourAnimation")
arrowShapeLayer.path = direction.arrowPath(in: bounds).cgPath
arrowShapeLayer.strokeColor = direction.arrowColour.cgColor
}
}
Third Step
The third step is to synchronize Core Animation with UIKit animation by requesting the CAAction
property.
var direction: Direction = .up {
didSet {
guard oldValue != direction else { return }
if let backgroundColourAnimation = action(for: layer, forKey: "backgroundColor") as? CABasicAnimation {
let pathAnimation = backgroundColourAnimation.copy(forKeyPath: "path")
pathAnimation.fromValue = arrowShapeLayer.presentation()?.path
arrowShapeLayer.add(pathAnimation, forKey: "pathAnimation")
let strokeColourAnimation = backgroundColourAnimation.copy(forKeyPath: "strokeColor")
strokeColourAnimation.fromValue = arrowShapeLayer.presentation()?.strokeColor
arrowShapeLayer.add(strokeColourAnimation, forKey: "strokeColourAnimation")
}
arrowShapeLayer.path = direction.arrowPath(in: bounds).cgPath
arrowShapeLayer.strokeColor = direction.arrowColour.cgColor
}
}
Resources
If you are curious, I would recommend you read a more detailed explanation from Darjeeling Steve’s blog. I found this resource incredible and full of comprehensive information.