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

SwiftUI中ObservedObject状态变更时避免Timer重置的技术问询

解决方案:确保Timer稳定运行不受View重建影响

你的核心问题在于View重建导致Timer或其ViewModel被重新初始化,以及Timer的管理方式不符合SwiftUI的响应式范式。下面是分步骤的修复方案:

1. 用@StateObject替代@ObservedObject存储TimerViewModel

在ContentView中,你当前用@ObservedObject声明timerData,这会导致每次ContentView因apptCardVM状态变化而重建时,timerData都会被重新初始化,直接重置Timer。

修改ContentView的声明:

struct ContentView: View {
    @ObservedObject var apptCardVM: ApptCardViewModel
    // 用@StateObject确保ViewModel仅初始化一次,生命周期与View绑定
    @StateObject var timerData = TimerDataViewModel()
    
    var body: some View {
        VStack {
            CurrentDateView(timerData: timerData)
            Picker("Seizure Type", selection: $apptCardVM.typeIndex) {
                ForEach(0..<apptCardVM.typeChoice.count) {
                    Text(self.apptCardVM.typeChoice[$0])
                }
            }.pickerStyle(SegmentedPickerStyle())
        }
    }
}

@StateObject是iOS14+引入的,专门用于在View内部持有ObservableObject,确保其仅被创建一次,不受View重建影响。

2. 重构TimerDataViewModel,用Combine管理Timer

原来的Timer.scheduledTimer方式不够灵活,且容易在View重建时丢失引用。改用Combine的Timer.publish配合AnyCancellable来管理Timer的生命周期:

class TimerDataViewModel: ObservableObject {
    @Published var secondsElapsed = 0.0
    @Published var minutesElapsed = 0.0
    @Published var hoursElapsed = 0.0
    
    // 用Cancellable管理Timer的订阅,避免内存泄漏
    private var timerCancellable: AnyCancellable?
    
    init() {
        print("TimerViewModel initialized once")
    }
    
    func start() {
        // 防止重复启动Timer
        guard timerCancellable == nil else { return }
        
        let timer = Timer.publish(every: 1, on: .main, in: .common)
            .autoconnect()
        
        timerCancellable = timer.sink { [weak self] _ in
            guard let self = self else { return }
            self.secondsElapsed += 1
            
            // 计算时分秒
            self.hoursElapsed = floor(self.secondsElapsed / 3600)
            self.minutesElapsed = floor((self.secondsElapsed.truncatingRemainder(dividingBy: 3600)) / 60)
            self.secondsElapsed = self.secondsElapsed.truncatingRemainder(dividingBy: 60)
        }
    }
    
    // 可选:添加停止Timer的方法
    func stop() {
        timerCancellable?.cancel()
        timerCancellable = nil
    }
}

这里的关键点:

  • 用timerCancellable持有Timer的订阅,确保Timer在ViewModel存活期间持续运行
  • 启动前检查timerCancellable是否为空,避免重复创建Timer
  • 用[weak self]避免循环引用

3. 调整CurrentDateView,确保Timer仅启动一次

原来的onAppear可能在View重建时被多次触发,但现在因为timerData是@StateObject,且ViewModel内部做了重复启动的防护,所以可以保留onAppear,或者直接在ViewModel的init里启动Timer(根据你的需求):

struct CurrentDateView: View {
    @ObservedObject var timerData: TimerDataViewModel
    
    var body: some View {
        Text("\(Int(timerData.hoursElapsed), specifier: \"%02d\"):\(Int(timerData.minutesElapsed), specifier: \"%02d\"):\(Int(timerData.secondsElapsed), specifier: \"%02d\")")
            .fontWeight(.bold)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .onAppear(){
                timerData.start()
            }
    }
}

现在即使CurrentDateView因父View重建而重新创建,它引用的timerData是同一个ViewModel实例,start()方法会跳过重复启动,Timer会持续运行。

为什么这个方案有效?

  • @StateObject确保TimerDataViewModel仅初始化一次,不会随父View重建而重置
  • Combine的Timer订阅由ViewModel内部管理,不受View生命周期的直接影响
  • 重复启动的防护逻辑避免了Timer被多次创建

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 15:32:41