I have a progress method here:
func progress(incremented : CGFloat){
if incremented <= self.bounds.width{
self.progressLayer.removeFromSuperlayer()
let originBezierPathProg = UIBezierPath(roundedRect: CGRect(x:0, y:0, width:0, height:self.bounds.height) , cornerRadius: self.viewCornerRadius)
originBezierPathProg.close()
let newBezierPathProg = UIBezierPath(roundedRect: CGRect(x:0, y:0, width:incremented, height:self.bounds.height) , cornerRadius: self.viewCornerRadius)
bezierPathProg.close()
self.progressLayer.path = originBezierPathProg.cgPath
self.borderLayer.addSublayer(self.progressLayer)
let animation = CABasicAnimation(keyPath: "path")
animation.fromValue = originBezierPathProg.cgPath
animation.toValue = newBezierPathProg.cgPath
animation.duration = 1
self.progressLayer.add(animation, forKey: animation.keyPath)
self.progressLayer.path = newBezierPathProg.cgPath
}
}
I am trying to make progress bar progress in an animated way. But when I call progress(100)
, it's simply rendering the bar without animation.
How can I fix it?
Update: Created MCVE as per Rob's suggestion: https://github.com/utkarsh2012/ProgressBarTest . I expect the progress bar to animate from width=0 to width=x (say 60)
Looks similar to this problem CABasicAnimation with CALayer path doesn't animate
The progress
method originally shown in your question is fine. The problem is in how you used it. That having been said, let's go through the MCVE.
In your MCVE, there were a few things preventing the animation, namely:
The progress
method was as follows:
func progress(incremented: CGFloat) {
if incremented <= bounds.width {
let toPath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: incremented + 100, height: bounds.height), cornerRadius: viewCornerRadius)
progressLayer.path = toPath.cgPath
borderLayer.addSublayer(progressLayer)
let animation = CABasicAnimation(keyPath: "path")
animation.fromValue = self.progressLayer.path
animation.toValue = toPath.cgPath
animation.duration = 3
progressLayer.add(animation, forKey: animation.keyPath)
}
}
That is setting the path
to be the new path, and then animating from that to the new path (i.e. animating to and from the same path). Thus, no animation.
Save the old path before changing the path
to its new "end state" and initiating the animation. Use that saved path as the fromValue
of the animation.
The value
property was calling layoutSubviews
:
var value: Int? {
didSet {
progress(value: value!)
self.layoutSubviews()
}
}
Never call layoutSubviews()
directly.
The view controller was doing the following:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let view = DetailView(frame: self.view.frame)
view.value = 60
self.containerView.addSubview(view)
}
Don't try to start animations in viewDidLoad
. That's too early in the view lifecycle. Use viewDidAppear
(or whatever) instead. Doing this in viewDidLoad
is generally too early in the view lifecycle.
Related to that viewDidLoad
code, you were changing the value
of the DetailView
(which triggered the animation) before you've added it to the view hierarchy.
See the fixes here https://github.com/robertmryan/ProgressBarTest.