Search code examples
iosswiftanimationcore-graphicscashapelayer

Swift 3: Animate color fill of arc added to UIBezierPath


I wish to animate the color fill of a section of a pie chart. I create the pie chart by creating a UIBezierPath() for each piece of the pie and then use the addArc method to specify the size/constraints of the arc. To animate the pie chart segment, I want the color fill of the arc to animate from the center of the circle to the radius end. However, I am having trouble. I heard the strokeEnd keyPath animated from 0 to 1 should work, but there is no animation happening on the arcs (the arcs are just appearing at app launch).

let rad = 2 * Double.pi
let pieCenter: CGPoint = CGPoint(x: frame.width / 2, y: frame.height / 2)
var start: Double = 0
for i in 0...data.count - 1 {
    let size: Double = Double(data[i])! / 100 // the percentege of the circle that the given arc will take
    let end: Double = start + (size * rad)

    let path = UIBezierPath()
    path.move(to: pieCenter)
    path.addArc(withCenter: pieCenter, radius: frame.width / 3, startAngle: CGFloat(start), endAngle: CGFloat(end), clockwise: true)

    start += size * rad

    let lineLayer = CAShapeLayer()
    lineLayer.bounds = self.bounds
    lineLayer.position = self.layer.position
    lineLayer.path = path.cgPath
    lineLayer.strokeColor = UIColor.white.cgColor
    lineLayer.fillColor = colors[i]
    lineLayer.lineWidth = 0

    self.layer.addSublayer(lineLayer)

    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    animation.fillMode = kCAFillModeForwards
    animation.fromValue = pieCenter
    animation.toValue = frame.width / 3 // radius
    animation.duration = 2.5

    lineLayer.add(animation, forKey: nil)
}

I've seen a solution to a similar problem here, but it does not work for the individual arcs.


Solution

  • In order to ensure a smooth, clockwise animation of the pie chart, you must perform the following steps in order:

    • Create a new parent layer of type CAShapeLayer
    • In a loop each pie chart slice to the parent layer
    • In another loop, iterate through the sublayers (pie slices) of the parent layer and assign each sublayer a mask and animate that mask in the loop
    • Add the parent layer to the main layer: self.layer.addSublayer(parentLayer)

    In a nutshell, the code will look like this:

    // Code above this line adds the pie chart slices to the parentLayer
    for layer in parentLayer.sublayers! {
        // Code in this block creates and animates the same mask for each layer
    }
    

    Each animation applied to each pie slice will be a strokeEnd keypath animation from 0 to 1. When creating the mask, be sure its fillColor property is set to UIColor.clear.cgColor.