SwiftUI计时器异常:自动加30分钟至倒计时且时间随机跳转
SwiftUI计时器时间异常问题修复
问题现象
修改YouTube博主Indently的计时器教程代码,实现固定5分钟倒计时,但运行时点击Start后显示35分钟,再次点击时间随机变化。
问题原因
- 时区偏移导致时间计算错误:通过
Date(timeIntervalSince1970: diff)转换剩余秒数为Date后,使用本地日历取分钟组件,会引入时区/夏令时的偏移,导致分钟数异常。 - 重复点击Start时错误累加时间:
start方法中每次点击都会在当前endDate基础上添加分钟,而非从当前时间重新计算结束时间。 - 不必要的参数传递:目标是固定5分钟倒计时,却仍传递
vm.minutes作为参数,可能引入变量干扰。
修复代码
修复后的ViewModel
import Foundation extension TimerView { final class ViewModel: ObservableObject { @Published var isActive = false @Published var showingAlert = false @Published var time: String = "5:00" @Published var minutes: Float = 5.0 { didSet { self.time = "\(Int(minutes)):00" } } let initialTime = 5 // 固定初始时间为5分钟 var endDate = Date() // 启动计时器,固定5分钟 func start() { self.endDate = Calendar.current.date(byAdding: .minute, value: initialTime, to: Date())! self.isActive = true } // 重置计时器 func reset() { self.minutes = Float(initialTime) self.isActive = false self.time = "\(Int(minutes)):00" } // 更新倒计时显示 func updateCountdown(){ guard isActive else { return } let now = Date() let diff = endDate.timeIntervalSince(now) if diff <= 0 { self.isActive = false self.time = "0:00" self.showingAlert = true return } // 直接通过秒数计算分钟和秒,避免时区问题 let totalSeconds = Int(diff) let remainingMinutes = totalSeconds / 60 let remainingSeconds = totalSeconds % 60 self.minutes = Float(remainingMinutes) self.time = String(format:"%d:%02d", remainingMinutes, remainingSeconds) } } }
修复后的TimerView
import SwiftUI struct TimerView: View { @ObservedObject var vm = ViewModel() let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() let width: Double = 250 var body: some View { VStack { Text("Timer: \(vm.time)") .font(.system(size: 50, weight: .medium, design: .rounded)) .alert("Timer done!", isPresented: $vm.showingAlert) { Button("Continue", role: .cancel) { vm.reset() // 弹窗关闭后重置计时器 } } .padding() HStack(spacing:50) { Button("Start") { // 点击Start时,无论当前状态都重置并启动5分钟倒计时 vm.reset() vm.start() } .padding() .background(Color(red: 184/255, green: 243/255, blue: 255/255)) .foregroundColor(.black) .cornerRadius(10) .font(Font.system(size: UIFontMetrics.default.scaledValue(for: 16))) if vm.isActive { Button("Pause") { vm.isActive = false } .padding() .foregroundColor(.black) .background(.red) .cornerRadius(10) .font(Font.system(size: UIFontMetrics.default.scaledValue(for: 16))) } else { Button("Resume") { vm.isActive = true } .padding() .foregroundColor(.black) .background(.green) .cornerRadius(10) .font(Font.system(size: UIFontMetrics.default.scaledValue(for: 16))) } } .frame(width: width) } .onReceive(timer) { _ in vm.updateCountdown() } } } struct TimerView_Previews: PreviewProvider { static var previews: some View { TimerView() } }
关键修复点
- 直接通过总秒数计算剩余分钟和秒数,彻底规避时区/夏令时带来的时间偏移。
start方法固定使用初始5分钟,从当前时间重新计算结束时间,避免重复点击累加时间。- 优化Start按钮逻辑:点击时先重置再启动,确保每次点击都触发全新的5分钟倒计时。
- 弹窗关闭后自动重置计时器,提升用户体验。
内容的提问来源于stack exchange,提问作者Zudoturiku
相关产品推荐
相关产品推荐

