如何根据剩余时间优化0.1至0.9区间的Circle.trim实现?
修复SwiftUI环形计时器的进度条逻辑,替换硬凑的魔法数值
你当前的代码靠反复调数值让进度条“看起来正常”,但核心问题是第二个Circle的trim用了1.1、0.026、1.25这类无逻辑的魔法数值,完全没法复用,也搞不清背后的计算逻辑。咱们把这部分重构,用明确的比例计算来替代。
先搞懂环形进度的基础逻辑
SwiftUI里Circle().trim(from:to:)的参数是0到1的比例,对应圆周的0%到100%。你给两个圆环加了.rotationEffect(.degrees(90)),是把默认的右侧起始点转到了顶部,这个没问题。
你的背景环是trim(from:0.1, to:0.9),也就是占了圆周的80%长度(0.9-0.1=0.8)。所以进度条的范围必须和这个背景环对齐:从0.1开始,到0.1 + 0.8 * 剩余时间比例结束,这样就能完美匹配背景环的长度。
重构后的代码
struct ContentView: View { @State private var timeRemaining: TimeInterval = 10 @State private var startTime: TimeInterval = 10 // 初始化时直接和总时长一致 @State private var timer: Timer? @State private var isRunning: Bool = false // 计算剩余时间的比例:0(时间到)~1(满时长) private var progress: Double { timeRemaining / startTime } var body: some View { ZStack { // 背景环:固定显示80%的圆周 Circle() .trim(from: 0.1, to: 0.9) .stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round)) .rotationEffect(.degrees(90)) .opacity(0.3) // 进度环:和背景环对齐,根据剩余比例计算结束位置 Circle() .trim(from: 0.1, to: CGFloat(0.1 + (0.9 - 0.1) * progress)) .stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round)) .rotationEffect(.degrees(90)) .animation(.easeInOut(duration: 0.8), value: timeRemaining) // 绑定timeRemaining,每次变化自动动画 Text(formattedTime()) .font(.largeTitle) .bold() } .frame(width: 250) Button { isRunning.toggle() if isRunning { startTimer() } else { stopTimer() } } label: { Image(systemName: isRunning ? "stop.fill" : "play.fill") .foregroundStyle(.foreground) .frame(width: 50, height: 50) .font(.largeTitle) .padding() } } private func startTimer() { // 防止重复启动 guard timer == nil else { return } // 如果是重新开始,更新startTime为当前的总时长 startTime = timeRemaining timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in if timeRemaining > 0 { timeRemaining -= 1 } else { stopTimer() } } } private func stopTimer() { isRunning = false timer?.invalidate() timer = nil // 清空timer引用 timeRemaining = 10 startTime = 10 } private func formattedTime() -> String { let minutes = Int(timeRemaining) / 60 let seconds = Int(timeRemaining) % 60 return String(format: "%02d:%02d", minutes, seconds) } }
关键改动说明
- 新增
progress计算属性:直接用timeRemaining / startTime得到0到1的比例,逻辑清晰,不管总时长设成10秒还是60秒都能正常工作。 - 进度环的
trim计算:0.1 + (0.9 - 0.1) * progress,其中(0.9-0.1)是背景环的长度(80%),乘以剩余比例后加到起始点0.1上,完美匹配背景环的范围。 - 动画优化:把动画绑定到
timeRemaining,每次数值变化自动触发动画,替代原代码里固定1秒的动画(原动画会导致每秒的进度变化都强制1秒动画,和timer的1秒间隔冲突)。 - Timer管理优化:添加
guard timer == nil else { return }防止重复启动,清空timer引用避免内存泄漏。
这样修改后,你完全不用再调那些莫名其妙的数值,逻辑一目了然,不管怎么改总时长,进度条都会准确对应剩余时间。
内容的提问来源于stack exchange,提问作者ALX
相关产品推荐
相关产品推荐

