圆形进度计时器动画在Segue跳转后无法启动
问题解决:Segue跳转后圆形进度动画不启动
核心原因分析
- 视图生命周期时机错误:
viewDidLoad执行时,视图尚未完成布局或未加入窗口的图层树,系统无法正确触发图层动画。 - 动画状态未保留:默认情况下CAAnimation执行完毕后会被移除,图层重置为初始状态,视觉上看起来动画没启动。
- 布局计算时机不对:
viewDidLoad阶段view.frame可能因Auto Layout未生效而不准确,导致圆形路径绘制位置错误,动画即使执行也无法看到。
分步修复方案
1. 调整动画启动时机到viewDidAppear
视图通过Segue跳转后,viewDidAppear会在视图完全显示时触发,此时图层已加入窗口图层树,动画能被正确调度。同时添加动画状态保留配置:
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // 配置动画参数 strokeIt.fromValue = 0 strokeIt.toValue = 1 strokeIt.duration = timeLeft // 绑定timeLeft变量,避免硬编码 // 关键配置:动画完成后保持最终状态 strokeIt.fillMode = .forwards strokeIt.removedOnCompletion = false timeLeftShapeLayer.add(strokeIt, forKey: "strokeEndAnimation") // 启动计时器 endTime = Date().addingTimeInterval(timeLeft) timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true) }
2. 移至viewDidLayoutSubviews绘制路径
viewDidLayoutSubviews会在视图布局完成后调用,此时view.frame是准确的,能保证圆形路径绘制在正确位置。同时避免重复添加图层:
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() drawBgShape() drawTimeLeftShape() // 更新时间标签位置 timeLabel.frame = CGRect(x: view.frame.midX-50 ,y: view.frame.midY-25, width: 100, height: 50) } // 修改绘制方法,先移除旧图层再添加 func drawBgShape() { bgShapeLayer.removeFromSuperlayer() bgShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: view.frame.midX , y: view.frame.midY), radius: 150, startAngle: (3*CGFloat.pi) / 2, endAngle: -CGFloat.pi / 2, clockwise: false).cgPath bgShapeLayer.strokeColor = UIColor.blue.cgColor bgShapeLayer.fillColor = UIColor.clear.cgColor bgShapeLayer.lineWidth = 15 view.layer.addSublayer(bgShapeLayer) } func drawTimeLeftShape() { timeLeftShapeLayer.removeFromSuperlayer() timeLeftShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: view.frame.midX , y: view.frame.midY), radius: 100, startAngle: (3*CGFloat.pi) / 2, endAngle: -CGFloat.pi / 2, clockwise: false).cgPath timeLeftShapeLayer.strokeColor = UIColor.white.cgColor timeLeftShapeLayer.fillColor = UIColor.clear.cgColor timeLeftShapeLayer.lineWidth = 15 timeLeftShapeLayer.strokeEnd = 0 // 初始状态设为0,确保动画从起点开始 view.layer.addSublayer(timeLeftShapeLayer) }
3. 简化viewDidLoad逻辑
只保留基础初始化,移除绘制和动画启动代码:
override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor(white: 0.94, alpha: 1.0) addTimeLabel() }
完整修正代码
import UIKit class TimerPage: UIViewController { let timeLeftShapeLayer = CAShapeLayer() let bgShapeLayer = CAShapeLayer() var timeLeft: TimeInterval = 60 var endTime: Date? var timeLabel = UILabel() var timer = Timer() let strokeIt = CABasicAnimation(keyPath: "strokeEnd") override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor(white: 0.94, alpha: 1.0) addTimeLabel() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() drawBgShape() drawTimeLeftShape() timeLabel.frame = CGRect(x: view.frame.midX-50 ,y: view.frame.midY-25, width: 100, height: 50) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) strokeIt.fromValue = 0 strokeIt.toValue = 1 strokeIt.duration = timeLeft strokeIt.fillMode = .forwards strokeIt.removedOnCompletion = false timeLeftShapeLayer.add(strokeIt, forKey: "strokeEndAnimation") endTime = Date().addingTimeInterval(timeLeft) timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true) } func drawBgShape() { bgShapeLayer.removeFromSuperlayer() bgShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: view.frame.midX , y: view.frame.midY), radius: 150, startAngle: (3*CGFloat.pi) / 2, endAngle: -CGFloat.pi / 2, clockwise: false).cgPath bgShapeLayer.strokeColor = UIColor.blue.cgColor bgShapeLayer.fillColor = UIColor.clear.cgColor bgShapeLayer.lineWidth = 15 view.layer.addSublayer(bgShapeLayer) } func drawTimeLeftShape() { timeLeftShapeLayer.removeFromSuperlayer() timeLeftShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: view.frame.midX , y: view.frame.midY), radius: 100, startAngle: (3*CGFloat.pi) / 2, endAngle: -CGFloat.pi / 2, clockwise: false).cgPath timeLeftShapeLayer.strokeColor = UIColor.white.cgColor timeLeftShapeLayer.fillColor = UIColor.clear.cgColor timeLeftShapeLayer.lineWidth = 15 timeLeftShapeLayer.strokeEnd = 0 view.layer.addSublayer(timeLeftShapeLayer) } func addTimeLabel() { timeLabel.textAlignment = .center timeLabel.text = timeLeft.time view.addSubview(timeLabel) } @objc func updateTime() { if timeLeft > 0 { timeLeft = endTime?.timeIntervalSinceNow ?? 0 timeLabel.text = timeLeft.time } else { timeLabel.text = "00:00" timer.invalidate() } } } extension TimeInterval { var time: String { return String(format:"%02d:%02d", Int(self/60), Int(ceil(truncatingRemainder(dividingBy: 60))) ) } }
内容的提问来源于stack exchange,提问作者The Mega Gudetama
相关产品推荐
相关产品推荐

