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

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)
        }
    }
}

关键说明

  1. 可观察属性:ViewModel中所有需要View响应的属性必须添加@Published,否则View无法感知状态变化,这是你之前布尔值判断无效的核心原因。
  2. 视图切换:使用navigationDestination(或sheet/fullScreenCover),确保跳转逻辑嵌入到NavigationStack/NavigationView层级内,避免“视图结果未被使用”的错误。
  3. 后台逻辑优化:在movingToForeground中增加了后台期间计时器归零的判断,避免回到前台时状态不一致。

内容的提问来源于stack exchange,提问作者Shana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 06:01:26