Thanks, Duncan C - that was super helpful.
Here is an example of a progress circle using your suggestion.
The view draws a progress circle that animates over time, starting out as a full circle and erasing itself to reveal the view behind the circle. There are @IBInspectable properties that can be modified to fill the circle instead of erasing.
Here is a link to the project. The view is in CircleTimer.view. If you run the project, you can enter a number of seconds and see the circle erase over the specified time.
https://github.com/riley2012/circle-timer-ios
This is the method that does the animation:
open func runMaskAnimation(duration: CFTimeInterval) {
if let parentLayer = filledLayer {
let maskLayer = CAShapeLayer()
maskLayer.frame = parentLayer.frame
let circleRadius = timerFillDiameter * 0.5
let circleHalfRadius = circleRadius * 0.5
let circleBounds = CGRect(x: parentLayer.bounds.midX - circleHalfRadius, y: parentLayer.bounds.midY - circleHalfRadius, width: circleRadius, height: circleRadius)
maskLayer.fillColor = UIColor.clear.cgColor
maskLayer.strokeColor = UIColor.black.cgColor
maskLayer.lineWidth = circleRadius
let path = UIBezierPath(roundedRect: circleBounds, cornerRadius: circleBounds.size.width * 0.5)
maskLayer.path = path.reversing().cgPath
maskLayer.strokeEnd = 0
parentLayer.mask = maskLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 1.0
animation.toValue = 0.0
maskLayer.add(animation, forKey: "strokeEnd")
}
}