You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

圆形进度计时器动画在Segue跳转后无法启动

问题解决:Segue跳转后圆形进度动画不启动

核心原因分析

  1. 视图生命周期时机错误:viewDidLoad执行时,视图尚未完成布局或未加入窗口的图层树,系统无法正确触发图层动画。
  2. 动画状态未保留:默认情况下CAAnimation执行完毕后会被移除,图层重置为初始状态,视觉上看起来动画没启动。
  3. 布局计算时机不对: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 04:45:35