I have a UIView subclass, which draws an arc inside its frame. The draw method looks like this:
override func draw(_ rect: CGRect) {
let clockwise = true
let center: CGPoint = CGPoint(x: rect.midX, y: rect.midY)
let radius: CGFloat = rect.width / 2
self.trackWidth = min(max(trackWidth, 1), radius)
arc = UIBezierPath(arcCenter: center,
radius: radius - (trackWidth / 2),
startAngle: startAngle!,
endAngle: endAngle!,
clockwise: clockwise)
arc!.lineWidth = trackWidth
arc!.lineCapStyle = .round
self.color.setStroke()
arc!.stroke()
return
}
This code was pulled from another SO'flow post, but it works fine and I'm using it to draw static arcs in an interface.
My question is, how can I animate the arcs, and specifically the startAngle of the UIBezierPath? I gather so far that this cannot be done with a regular UIView animation, so CABasicAnimation seems to be the go-to. But I can't figure out how to specify the property to be animated in a block of code like this:
func animateArc() {
CATransaction.begin()
var arc = arcs.first!
DemoView.animate(withDuration: 2.0) {
arc.layoutIfNeeded()
}
let animation = CABasicAnimation(keyPath: "startAngle")
animation.fromValue = 0
animation.toValue = DemoView.radianizer(40)
animation.autoreverses = false
animation.isRemovedOnCompletion = false
arc.layer.add(animation, forKey: "startAngle")
CATransaction.commit()
}
Any help gratefully received.
Rather than drawing the path in draw
, you can use a layer-based approach. Add a CAShapeLayer
as a sublayer of your view.
let arcLayer = CAShapeLayer()
arcLayer.path = yourFullArc
arcLayer.strokeColor = self.color.cgColor
arcLayer.fillColor = UIColor.clear.cgColor
arcLayer.lineWidth = trackWidth
arcLayer.lineCap = .round
self.layer.addSublayer(arcLayer)
Then this sublayer's strokeStart
can be animated, which is kind of like animating the start angle. The value of strokeStart
and strokeEnd
ranges from 0 to 1, and they basically mean "which part of the line do you want to be stroked". Now you can convert the "from" and "to" values 0 and 40 to values for strokeStart
.
The layer can then be animated like this:
// calculating values for strokeStart
let angleRange = endAngle! - startAngle!
let from = startAngle! / angleRange
let to = (startAngle! + DemoView.radianizer(40)) / angleRange
let animation = CABasicAnimation(keyPath: "strokeStart")
animation.fromValue = from
animation.toValue = to
animation.autoreverses = false
animation.isRemovedOnCompletion = false
arcLayer.add(animation, forKey: "someAnimation")