I have a circular UIBezierPath & 2x CAShapeLayers that I use to display the progress towards a specific milestone in one of my apps.
Problem: when I animate the progress via CABasicAnimation, the progress goes beyond what it should be doing.
Context: I display this circularView in a subview of a custom cell inside my milestonesCollectionView.
View Hierarchy: CollectionView > Custom Cell > subview > drawRect to create & add the layers there.
My code:
Custom Cell
Inside my custom cell I setup the currentProgress (hardcoded for now for testing purpose)
let placeHolderForProgressView: CircularTrackerView = {
let ctv = CircularTrackerView()
ctv.backgroundColor = UIColor.clear
ctv.translatesAutoresizingMaskIntoConstraints = false
ctv.currentProgress = 0.5 //set to 0.5 to get a circle filled at 50%
return ctv
}()
Inside CircularTrackerView
// I animate the progress whenever the cell is set and provides the currentProgress to the progressView
var currentProgress: CGFloat = 0.0 {
didSet {
animate(to: currentProgress)
let percent = Int(currentProgress * 100)
percentLbl.text = "\(percent)%"
}
}
let shapeLayer = CAShapeLayer() //displays the progress
let trackLayer = CAShapeLayer() //background of the ring
override func draw(_ rect: CGRect) {
super.draw(rect)
let center = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
let circularPath = UIBezierPath(arcCenter: center, radius: frame.size.width / 2, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.white.withAlphaComponent(0.2).cgColor
trackLayer.lineWidth = 5
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineCap = kCALineCapRound
layer.addSublayer(trackLayer)
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor.white.cgColor
shapeLayer.lineWidth = 5
shapeLayer.strokeEnd = 0
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineCap = kCALineCapRound
layer.addSublayer(shapeLayer)
}
private func animate(to progress: CGFloat) {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0.0
animation.toValue = progress
animation.duration = 2
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
shapeLayer.add(animation, forKey: "randomString")
}
Output
The circle goes beyond the 50% mark ... even tough I think I followed the right steps to create this (slightly adapted from the LetsBuildThatApp tutorial on YouTube: https://www.youtube.com/watch?v=O3ltwjDJaMk)
Thanks in advance if you can give any input that would help.
The problem is your UIBezierPath
/ the angle you give it:
let circularPath = UIBezierPath(arcCenter: center, radius: frame.size.width / 2, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
The difference endAngle - startAngle
is 2.5 * PI
but should only be 2 * PI
. When it is 2.5 * PI
that means your strokeEnd
of 0.5
results in 1.25 * PI
to be stroked.
Solution, reduce the endAngle
by 0.5 * PI
:
let circularPath = UIBezierPath(arcCenter: center, radius: frame.size.width / 2, startAngle: -0.5 * CGFloat.pi, endAngle: 1.5 * CGFloat.pi, clockwise: true)