Only one color
If you want to animate the angle of a solid colored pie segment, like the one asked in your question, you can do this by animating strokeEnd CAShapeLayer .
The "trick" here is to make a very wide line. More specifically, you can create a path that is only an arc (the dashed line in the animation below), at half the radius, and then gives it the full radius as the width of its line. When you live stroking this line, it looks like an orange segment below:

Depending on your use case, you can:
- create a path from one corner to another corner and animate the course of the end from 0 to 1
- create a path for the full circle, set the initial move and the end of the stroke to some part of the circle, and the animated move ends from the initial to the final fraction.
If your picture is only one color, then this will be the smallest solution to your problem.
However, if your drawing is more complex (for example, also stroking a pie segment), then this solution simply will not work, and you will have to do something more complicated.
Custom Drawing / Custom Animations
If your drawing of the pie segment is more complex, you will quickly find that you need to create a subclass of the level with custom animated properties. This is a bit more code, some of which may look somewhat unusual 1 - but not as scary as it might seem.
- This may be one of those things that is even more convenient to do in Objective-C.
Dynamic properties
First, subclass the level with the properties you need. In an Objective-C expression, these properties must be @dynamic , i.e. Not synthesized. This is not the same as dynamic in Swift. Instead, we should use @NSManaged .
class PieSegmentLayer : CALayer { @NSManaged var startAngle, endAngle, strokeWidth: CGFloat @NSManaged var fillColor, strokeColor: UIColor?
This allows Core Animation to dynamically process these properties, allowing you to track changes and integrate them into the animation system.
Note A good rule of thumb is that these properties should all be related to the drawing / visual representation of the layer. If this is not the case, then it is likely that they do not belong to the layer. Instead, they can be added to a view, which in turn uses a layer to draw it.
Copy layers
During custom animation, Core Animation will want to create and display different layer configurations for different frames. Unlike most other Apple infrastructures, this happens with the init(layer:) copy constructor. In order for these five properties to be copied, we need to override init(layer:) and copy their values.
In Swift, we must also override simple init() and init?(coder) .
override init(layer: Any) { super.init(layer: layer) guard let other = layer as? PieSegmentLayer else { return } fillColor = other.fillColor strokeColor = other.strokeColor startAngle = other.startAngle endAngle = other.endAngle strokeWidth = other.strokeWidth } override init() { super.init() } required init?(coder aDecoder: NSCoder) { return nil }
Reaction to change
Core Animation is largely built for performance. One way to achieve this is to avoid unnecessary work. By default, the layer will not be redrawn when the property changes. But these properties are used for drawing, and we want the layer to be redrawn when any of them changes. To do this, we need to override needsDisplay(forKey:) and return true if the key was one of these properties.
override class func needsDisplay(forKey key: String) -> Bool { switch key { case #keyPath(startAngle), #keyPath(endAngle), #keyPath(strokeWidth), #keyPath(fillColor), #keyPath(strokeColor): return true default: return super.needsDisplay(forKey: key) } }
In addition, if we want to use the default implicit animations for these properties, we need to override action(forKey:) to return a partially configured animation object. If we want some properties (for example, angles) to be implicitly animated, we need to return the animation only for these properties. If we donโt need something very ordinary, itโs useful to simply return the base animation with fromValue set to the current view value:
override func action(forKey key: String) -> CAAction? { switch key { case #keyPath(startAngle), #keyPath(endAngle): let anim = CABasicAnimation(keyPath: key) anim.fromValue = presentation()?.value(forKeyPath: key) return anim default: return super.action(forKey: key) } }
Drawing
The final piece of custom animation is a custom drawing. This is done by overriding draw(in:) and using the provided context to draw the layer:
override func draw(in ctx: CGContext) { let center = CGPoint(x: bounds.midX, y: bounds.midY) // subtract half the stroke width to avoid clipping the stroke let radius = min(center.x, center.y) - strokeWidth / 2 // The two angle properties are in degrees but CG wants them in radians. let start = startAngle * .pi / 180 let end = endAngle * .pi / 180 ctx.beginPath() ctx.move(to: center) ctx.addLine(to: CGPoint(x: center.x + radius * cos(start), y: center.y + radius * sin(start))) ctx.addArc(center: center, radius: radius, startAngle: start, endAngle: end, clockwise: start > end) ctx.closePath() // Configure the graphics context if let fillCGColor = fillColor?.cgColor { ctx.setFillColor(fillCGColor) } if let strokeCGColor = strokeColor?.cgColor { ctx.setStrokeColor(strokeCGColor) } ctx.setLineWidth(strokeWidth) ctx.setLineCap(.round) ctx.setLineJoin(.round) // Draw ctx.drawPath(using: .fillStroke) }
Here I filled and stroked the pie segment, which extends from the center of the layer to the nearest edge. You must replace this with your own drawing.
Custom animation in action
With all this code, we now have a custom level subclass whose properties can be animated both implicitly (simply by changing them) and explicitly (by adding CAAnimation for our key). The results look something like this:

Final words
This may not be obvious at the frame rate of these animations, but one strong advantage of using Core Animation (in different ways) in both of these solutions is that it separates the drawing of one state from the time of the animation.
This means that the layer does not and should not know about duration, delays, time curves, etc. All of them can be configured and controlled externally.