SwiftUI中计时器结束时如何实现视图切换?
解决方案
第一步:完善ViewModel的可观察状态
你的FocusBreakManager缺少标记计时器结束的可观察属性,而且现有需要被View感知的属性都没加@Published,导致View无法响应状态变化。修改后的ViewModel如下:
class FocusBreakManager: ObservableObject { // 所有需要View监听的属性都加@Published @Published var secondsElapsed = 1500 // 默认25分钟,可按需调整 @Published var focusfill = 0.0 @Published var focusTime = "" @Published var isTimerStarted = false @Published var isTimerFinished = false // 新增:标记计时器是否结束 var timer: Timer = Timer() func start() { guard !isTimerStarted else { return } isTimerStarted = true timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in guard let self = self else { return } self.secondsElapsed -= 1 self.focusfill += 0.01667 self.focusTime = self.formatCounter() if self.secondsElapsed <= 0 { self.stop() self.isTimerFinished = true // 计时器结束时标记状态 } } } func pause() { timer.invalidate() isTimerStarted = false } func stop() { timer.invalidate() isTimerStarted = false } func formatCounter() -> String { let minutes = Int(secondsElapsed) / 60 % 60 let seconds = Int(secondsElapsed) % 60 return String(format: "%02i : %02i", minutes, seconds) } // 重置计时器(从BreakView返回时可用) func reset() { secondsElapsed = 1500 focusfill = 0.0 focusTime = formatCounter() isTimerStarted = false isTimerFinished = false } }
第二步:修改MainView,实现视图切换逻辑
利用你已有的NavigationView(iOS 16+推荐用NavigationStack),使用navigationDestination触发视图跳转,同时修复“视图结果未被使用”的错误(该错误通常是因为视图切换代码未嵌入正确的View层级)。
struct MainView: View { @StateObject var fbManager = FocusBreakManager() // 用StateObject托管ViewModel @State private var notificationDate = Date() var body: some View { NavigationStack { VStack(alignment: .center, spacing: 50) { // 显示倒计时文本 Text(fbManager.focusTime) .font(.largeTitle) .foregroundColor(.white) Button(action: { if !fbManager.isTimerStarted { fbManager.start() } else { fbManager.pause() } }) { Image(systemName: fbManager.isTimerStarted ? "pause.fill" : "play.fill") .resizable() .scaledToFit() .frame(width: 50, height: 50) .foregroundColor(.white) } } .onAppear { fbManager.focusTime = fbManager.formatCounter() // 初始化显示倒计时 } // 监听后台/前台通知 .onReceive(NotificationCenter.default.publisher(for: UIScene.didEnterBackgroundNotification)) { _ in if fbManager.isTimerStarted { movingToBackground() } } .onReceive(NotificationCenter.default.publisher(for: UIScene.willEnterForegroundNotification)) { _ in if fbManager.isTimerStarted { movingToForeground() } } // 绑定计时器结束状态,跳转到BreakView .navigationDestination(isPresented: $fbManager.isTimerFinished) { BreakView(fbManager: fbManager) } .background(Color.gray) // 可选,方便区分视图 } } func movingToBackground() { print("Moving to the background") notificationDate = Date() fbManager.pause() } func movingToForeground() { print("Moving to the foreground") let deltaTime = Int(Date().timeIntervalSince(notificationDate)) fbManager.secondsElapsed -= deltaTime // 处理后台期间计时器归零的情况 if fbManager.secondsElapsed <= 0 { fbManager.stop() fbManager.isTimerFinished = true } else { fbManager.start() } } } // 示例BreakView,可按需自定义 struct BreakView: View { @ObservedObject var fbManager: FocusBreakManager @Environment(\.dismiss) private var dismiss // iOS 15+返回上一页 var body: some View { VStack(spacing: 50) { Text("休息时间!") .font(.largeTitle) Button("返回专注") { fbManager.reset() dismiss() } .font(.title) } } }
关键说明
- 可观察属性:ViewModel中所有需要View响应的属性必须添加
@Published,否则View无法感知状态变化,这是你之前布尔值判断无效的核心原因。 - 视图切换:使用
navigationDestination(或sheet/fullScreenCover),确保跳转逻辑嵌入到NavigationStack/NavigationView层级内,避免“视图结果未被使用”的错误。 - 后台逻辑优化:在
movingToForeground中增加了后台期间计时器归零的判断,避免回到前台时状态不一致。
内容的提问来源于stack exchange,提问作者Shana
相关产品推荐
相关产品推荐

