When you animate strokeEnd
, that animates the stroke around the path, but not the fill of the path.
If you're looking for just any animation of the fill, easy options include animating the fillColor
key path from UIColor.clear.cgColor
to the final color. Or animate the opacity
key path from 0 to 1.
func addPie(_ animated: Bool = true) {
shapeLayers.forEach { $0.removeFromSuperlayer() }
shapeLayers.removeAll()
guard let dataPoints = dataPoints else { return }
let center = pieCenter
let radius = pieRadius
var startAngle = -CGFloat.pi / 2
let sum = dataPoints.reduce(0.0) { $0 + $1.value }
for (index, dataPoint) in dataPoints.enumerated() {
let endAngle = startAngle + CGFloat(dataPoint.value / sum) * 2 * .pi
let path = closedArc(at: center, with: radius, start: startAngle, end: endAngle)
let shape = CAShapeLayer()
shape.fillColor = dataPoint.color.cgColor
shape.strokeColor = UIColor.black.cgColor
shape.lineWidth = lineWidth
shape.path = path.cgPath
layer.addSublayer(shape)
shapeLayers.append(shape)
shape.frame = bounds
if animated {
shape.opacity = 0
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) / Double(dataPoints.count)) {
shape.opacity = 1
let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 1
shape.add(animation, forKey: nil)
}
}
startAngle = endAngle
}
}
That yields:
The delaying of the animations give it a slightly more dynamic effect.
If you want to get fancy, you can play around with animations of transform
of the entire CAShapeLayer
. For example, you can scale the pie wedges:
func addPie(_ animated: Bool = true) {
shapeLayers.forEach { $0.removeFromSuperlayer() }
shapeLayers.removeAll()
guard let dataPoints = dataPoints else { return }
let center = pieCenter
let radius = pieRadius
var startAngle = -CGFloat.pi / 2
let sum = dataPoints.reduce(0.0) { $0 + $1.value }
for (index, dataPoint) in dataPoints.enumerated() {
let endAngle = startAngle + CGFloat(dataPoint.value / sum) * 2 * .pi
let path = closedArc(at: center, with: radius, start: startAngle, end: endAngle)
let shape = CAShapeLayer()
shape.fillColor = dataPoint.color.cgColor
shape.strokeColor = UIColor.black.cgColor
shape.lineWidth = lineWidth
shape.path = path.cgPath
layer.addSublayer(shape)
shapeLayers.append(shape)
shape.frame = bounds
if animated {
shape.opacity = 0
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) / Double(dataPoints.count) + 1) {
shape.opacity = 1
let animation = CABasicAnimation(keyPath: "transform")
animation.fromValue = CATransform3DMakeScale(0, 0, 1)
animation.toValue = CATransform3DIdentity
animation.duration = 1
shape.add(animation, forKey: nil)
}
}
startAngle = endAngle
}
}
Yielding:
Or you can rotate the pie wedge shape layer about its center angle making it appear to angularly expand:
func addPie(_ animated: Bool = true) {
shapeLayers.forEach { $0.removeFromSuperlayer() }
shapeLayers.removeAll()
guard let dataPoints = dataPoints else { return }
let center = pieCenter
let radius = pieRadius
var startAngle = -CGFloat.pi / 2
let sum = dataPoints.reduce(0.0) { $0 + $1.value }
for (index, dataPoint) in dataPoints.enumerated() {
let endAngle = startAngle + CGFloat(dataPoint.value / sum) * 2 * .pi
let path = closedArc(at: center, with: radius, start: startAngle, end: endAngle)
let shape = CAShapeLayer()
shape.fillColor = dataPoint.color.cgColor
shape.strokeColor = UIColor.black.cgColor
shape.lineWidth = lineWidth
shape.path = path.cgPath
layer.addSublayer(shape)
shapeLayers.append(shape)
shape.frame = bounds
if animated {
shape.opacity = 0
let centerAngle = startAngle + CGFloat(dataPoint.value / sum) * .pi
let transform = CATransform3DMakeRotation(.pi / 2, cos(centerAngle), sin(centerAngle), 0)
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) / Double(dataPoints.count)) {
shape.opacity = 1
let animation = CABasicAnimation(keyPath: "transform")
animation.fromValue = transform
animation.toValue = CATransform3DIdentity
animation.duration = 1
shape.add(animation, forKey: nil)
}
}
startAngle = endAngle
}
}
That yields:
I'd encourage you to not get too lost in the details of my CAShapeLayer
and my model, but rather focus on the CABasicAnimation
and the various keyPath
values we can animate.