Swift iOS:倒计时剩5秒时仅CALayer已进度部分变红问题
解决方案:实现剩余5秒时仅已进度环形变红
核心思路
要实现“剩余5秒时仅已完成进度段变红,背景层保持原色”的效果,核心是区分正常进度段和高亮进度段,通过双图层或动态切换进度层颜色来实现,避免修改整个图层的颜色。
以下提供两种可行方案,优先推荐双图层方案(更灵活,支持渐变高亮):
方案一:双进度层精准控制(推荐)
通过两个CAShapeLayer分别承载正常颜色进度和红色高亮进度,根据剩余时间动态切换显示逻辑:
1. 修改第三方库视图类
假设原库用单个progressLayer绘制进度,替换为两个进度层:
import UIKit class RingCountdownView: UIView { // 背景层(保持原逻辑不变) private let backgroundLayer = CAShapeLayer() // 正常颜色进度层 private let normalProgressLayer = CAShapeLayer() // 红色高亮进度层 private let highlightProgressLayer = CAShapeLayer() // 配置属性 var progressLineWidth: CGFloat = 8.0 var originalProgressColor: UIColor = .systemBlue var originalBackgroundColor: UIColor = .systemGray5 private var totalDuration: TimeInterval = 0 private var remainingTime: TimeInterval = 0 override init(frame: CGRect) { super.init(frame: frame) setupLayers() } required init?(coder: NSCoder) { super.init(coder: coder) setupLayers() } private func setupLayers() { // 背景层配置(原逻辑保留) backgroundLayer.lineWidth = progressLineWidth backgroundLayer.strokeColor = originalBackgroundColor.cgColor backgroundLayer.fillColor = UIColor.clear.cgColor backgroundLayer.lineCap = .round layer.addSublayer(backgroundLayer) // 正常进度层配置 normalProgressLayer.lineWidth = progressLineWidth normalProgressLayer.strokeColor = originalProgressColor.cgColor normalProgressLayer.fillColor = UIColor.clear.cgColor normalProgressLayer.lineCap = .round normalProgressLayer.strokeEnd = 0 layer.addSublayer(normalProgressLayer) // 高亮进度层配置(默认隐藏) highlightProgressLayer.lineWidth = progressLineWidth highlightProgressLayer.strokeColor = UIColor.systemRed.cgColor highlightProgressLayer.fillColor = UIColor.clear.cgColor highlightProgressLayer.lineCap = .round highlightProgressLayer.strokeStart = 0 highlightProgressLayer.strokeEnd = 0 highlightProgressLayer.isHidden = true layer.addSublayer(highlightProgressLayer) // 更新路径(和背景层一致) updateLayerPaths() } override func layoutSubviews() { super.layoutSubviews() updateLayerPaths() } private func updateLayerPaths() { let radius = min(bounds.width, bounds.height)/2 - progressLineWidth/2 let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: -CGFloat.pi/2, endAngle: CGFloat.pi*3/2, clockwise: true) backgroundLayer.path = path.cgPath normalProgressLayer.path = path.cgPath highlightProgressLayer.path = path.cgPath } // 对外暴露的更新方法 func updateCountdown(remainingTime: TimeInterval, totalDuration: TimeInterval) { self.remainingTime = remainingTime self.totalDuration = totalDuration let totalProgress = CGFloat((totalDuration - remainingTime)/totalDuration) let criticalProgress = CGFloat((totalDuration - 5)/totalDuration) // 剩余5秒时的已完成进度 if remainingTime > 5 { // 未到最后5秒:仅显示正常进度层 highlightProgressLayer.isHidden = true normalProgressLayer.strokeEnd = totalProgress } else { if totalProgress <= criticalProgress { // 进度还未进入最后5秒区间:继续显示正常进度 highlightProgressLayer.isHidden = true normalProgressLayer.strokeEnd = totalProgress } else { // 进入最后5秒:正常进度显示到临界值,高亮进度显示后续新增部分 highlightProgressLayer.isHidden = false normalProgressLayer.strokeEnd = criticalProgress highlightProgressLayer.strokeStart = criticalProgress highlightProgressLayer.strokeEnd = totalProgress } } } }
2. 业务代码中调用
// 初始化倒计时视图 let ringView = RingCountdownView(frame: CGRect(x: 100, y: 100, width: 100, height: 100)) view.addSubview(ringView) // 配置总时长和剩余时间 let totalDuration: TimeInterval = 60 // 总倒计时60秒 var remainingTime = totalDuration // 实时更新倒计时 let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] timer in guard let self = self else { return } remainingTime -= 0.1 if remainingTime <= 0 { timer.invalidate() remainingTime = 0 } ringView.updateCountdown(remainingTime: remainingTime, totalDuration: totalDuration) }
方案二:动态切换进度层颜色(简单版)
如果不需要区分“最后5秒前的进度”和“最后5秒内的进度”,仅需要剩余5秒时整个已进度段变红,可直接修改进度层颜色:
修改第三方库的进度更新方法
找到原库中更新进度的方法(比如updateProgress(to:)),添加颜色判断逻辑:
func updateProgress(to progress: CGFloat, remainingTime: TimeInterval) { // 背景层保持原颜色,不修改 backgroundLayer.strokeColor = originalBackgroundColor.cgColor // 根据剩余时间切换进度层颜色 let targetColor = remainingTime <= 5 ? UIColor.systemRed.cgColor : originalProgressColor.cgColor // 添加平滑颜色过渡动画(可选) let colorAnim = CABasicAnimation(keyPath: "strokeColor") colorAnim.fromValue = progressLayer.strokeColor colorAnim.toValue = targetColor colorAnim.duration = 0.3 progressLayer.add(colorAnim, forKey: "colorTransition") progressLayer.strokeColor = targetColor progressLayer.strokeEnd = progress }
注意事项
- 若不想直接修改第三方库源码,可通过继承原库视图类重写绘制逻辑,避免后续库更新冲突。
- 确保进度层的
lineWidth、lineCap等属性与背景层一致,避免显示错位。 - 若需要更流畅的动画,可使用
CADisplayLink替代Timer进行帧级更新。
内容的提问来源于stack exchange,提问作者nishant narola
相关产品推荐
相关产品推荐

