CABasicAnimation on CAShapeLayer not working for p

2019-06-07 22:34发布

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

1条回答
走好不送
2楼-- · 2019-06-07 23:00

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:

  1. 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.

  2. The value property was calling layoutSubviews:

    var value: Int? {
        didSet {
            progress(value: value!)
            self.layoutSubviews()
        }
    }
    

    Never call layoutSubviews() directly.

  3. 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.

  4. 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.

查看更多
登录 后发表回答