CABasicAnimation 有时有效,有时失败

CABasicAnimation Sometimes Works, Sometimes Fails

我正在制作一个应用程序,它可以引导用户通过一系列步骤完成帐户创建。每个步骤完成后,用户将被带到下一个视图控制器,屏幕顶部会出现一个进度条动画,以传达帐户创建过程已经完成了多少。这是最终结果:

这是通过将导航控制器放置在包含视图中来实现的。进度条位于包含视图之上,每次导航控制器推送一个新的视图控制器时,它都会告诉包含视图控制器将进度条设置为父视图宽度的特定百分比。这是通过以下 updateProgressBar 函数完成的。

import UIKit

class ContainerVC: UIViewController {
    var progressBar: CAShapeLayer!

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        progressBar = CAShapeLayer()
        progressBar.bounds = CGRect(x: 0, y: 0, width: 0, height: view.safeAreaInsets.top)
        progressBar.position = CGPoint(x: 0, y: 0)
        progressBar.anchorPoint = CGPoint(x: 0, y: 0)
        progressBar.backgroundColor = UIColor.secondaryColor.cgColor
        view.layer.addSublayer(progressBar)
    }

    func updateProgressBar(to percentAsDecimal: CGFloat!) {
        let newWidth = view.bounds.width * percentAsDecimal
        CATransaction.begin()
        CATransaction.setCompletionBlock({
            self.progressBar.bounds.size.width = newWidth
        })
        CATransaction.commit()
        let anim = CABasicAnimation(keyPath: "bounds")
        anim.isRemovedOnCompletion = true
        anim.duration = 0.25
        anim.fromValue = NSValue(cgRect: CGRect(x: 0, y: 0, width: progressBar.bounds.width, height: view.safeAreaInsets.top))
        anim.toValue = NSValue(cgRect: CGRect(x: 0, y: 0, width: newWidth, height: view.safeAreaInsets.top))
        progressBar.add(anim, forKey: "anim")
    }
}

导航控制器堆栈中的视图控制器将在推送下一个 VC 时调用此 updateProgressBar 函数。这是这样做的:

class FourthViewController: UIViewController {

    var containerVC: ContainerViewController!

    ...

    @IBAction func nextButtonPressed(_ sender: Any) {
        let storyboard = UIStoryboard(name: "Main", bundle: .main)
        let fifthVC = storyboard.instantiateViewController(withIdentifier: "FifthVC") as! FifthViewController
        fifthVC.containerVC = containerVC
        navigationController!.pushViewController(fifthVC, animated: true)
        //We pass 5/11 because the next step will be step 5 out of 11 total steps
        self.containerVC.updateProgressBar(to: 5/11)
    }
}

同样,当按下后退按钮时,我们缩小容器VC的进度条:

class FourthViewController: UIViewController {

    var containerVC: ContainerViewController!

    ...

    @IBAction func backButtonPressed(_ sender: Any) {
        navigationController!.popViewController(animated: true)

        //We pass 3/11 because the previous step is step 3 out of 11 total steps
        containerVC.updateProgressBar(to: 3/11)
    }
}

我的问题是这个动画只是有时有效。在此过程中向前移动时进度条始终有效,但有时,当用户向后导航时,进度条会卡住并且不再向任一方向移动,直到出现未到达的视图控制器。观看下面的视频:

Video of Bug (Bug begins around 0:23)

我已经确认 Alert Controller 的呈现不是动画失败的原因,并且还确保动画是在主线程上发生的。有什么建议吗?

正如此答案 中所解释的那样,viewDidLayoutSubviews() 被调用不止一次。

在您的情况下,每次从导航堆栈中推送或弹出视图控制器时,您最终都会实例化一个新的 CAShapeLayer

请尝试使用 viewDidAppear()