iOS开发:删除环形进度条动画后无法重新添加动画该如何解决?
故障原因
- 核心问题是
CAShapeLayer的模型层(Model Layer)属性和呈现层(Presentation Layer)属性不一致导致的:
动画中设置的isRemovedOnCompletion = false和fillMode = .forwards仅让呈现层停在了动画终点strokeEnd = 1的状态,从未修改过模型层的strokeEnd值,它从draw(_ rect: CGRect)调用后一直保持为初始值0。 - 调用
removeAllAnimations()后,呈现层会立刻回退到模型层的属性值,但新旧动画切换在同一个RunLoop周期内,新动画的起始值计算会出现异常,导致动画无法正常触发。 - 额外问题:
draw(_ rect: CGRect)方法中不应该存放progressLayer.strokeEnd = 0这类属性赋值逻辑,draw方法仅应负责路径、绘制相关的核心逻辑,非绘制相关的属性赋值放在这里会导致意外的属性重置。
修复方案
修改CircularProgressBar类代码即可,ViewController无需改动,修改后的完整代码如下:
@IBDesignable class CircularProgressBar: UIView { private var progressLayer = CAShapeLayer() private var backgroundMask = CAShapeLayer() @IBInspectable var ringWidth: CGFloat = 15 { didSet { backgroundMask.lineWidth = ringWidth progressLayer.lineWidth = ringWidth setNeedsDisplay() } } @IBInspectable var color: UIColor? = .purple { didSet { setNeedsDisplay() } } override init(frame: CGRect) { super.init(frame: frame) setupLayers() } required init?(coder: NSCoder) { super.init(coder: coder) setupLayers() } private func setupLayers() { backgroundMask.lineWidth = ringWidth backgroundMask.fillColor = nil backgroundMask.strokeColor = UIColor.gray.cgColor layer.addSublayer(backgroundMask) progressLayer.lineWidth = ringWidth progressLayer.fillColor = nil progressLayer.strokeColor = UIColor.yellow.cgColor // 初始strokeEnd移到初始化方法设置 progressLayer.strokeEnd = 0 layer.addSublayer(progressLayer) } override func draw(_ rect: CGRect) { // draw方法仅负责路径计算,使用当前视图bounds中点避免布局偏移 let circlePath = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: 140, startAngle: -(.pi / 2), endAngle: (3 * .pi / 2), clockwise: true).cgPath backgroundMask.path = circlePath progressLayer.path = circlePath } func addAnimation() { let duration: TimeInterval = 10 // 先重置模型层起始状态 progressLayer.strokeEnd = 0 // 提前设置模型层终点值,动画结束后自动停在终点 progressLayer.strokeEnd = 1 let animation = CABasicAnimation(keyPath: "strokeEnd") animation.fromValue = 0 animation.toValue = 1 animation.duration = duration progressLayer.add(animation, forKey: "strokeEndAnimation") } func restartCircularProgressBar() { progressLayer.removeAllAnimations() // 删除动画后显式重置模型层状态 progressLayer.strokeEnd = 0 } }
内容的提问来源于stack exchange,提问作者TeddyKen
相关产品推荐
相关产品推荐

