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

如何让NavigationStack中页面的.task任务保持活跃直至页面移除?

解决方案:让NavigationStack中的页面任务保持活跃

原代码里的计时器在导航到新页面后停止,是因为SwiftUI的.task修饰符会在视图离开屏幕(被导航覆盖)时自动取消任务。要实现“页面在导航栈中就保持任务活跃,被移除时才取消”,需要把任务的管理权移到父视图,绑定导航栈的路径变化来控制任务的生命周期。

修改步骤:

  1. 父视图统一管理ViewModel和任务:

    • 用字典存储每个计时器对应的TimerViewModel和运行任务,避免页面被覆盖时ViewModel和任务被销毁。
    • 通过监听导航栈路径的变化,在页面被从栈中移除时取消对应任务。
  2. 调整子视图的ViewModel获取方式:

    • 子视图不再自己创建@StateObject,而是从父视图接收@ObservedObject,确保ViewModel在页面被覆盖时依然存在。

修改后的完整代码:

struct TimerList: View {
  @State private var times = [0, 1, 2, 3, 4, 5]
  @State private var path: [Int] = []
  // 存储每个计时器对应的ViewModel
  @State private var timerViewModels: [Int: TimerViewModel] = [:]
  // 存储每个计时器的运行任务
  @State private var timerTasks: [Int: Task<Void, Never>] = [:]
  
  var body: some View {
    NavigationStack(path: $path) {
      List(times, id: \.self) { time in
        NavigationLink(value: time) {
          Text("\(time)")
        }
      }
      .navigationDestination(for: Int.self) { time in
        // 从父层获取或创建ViewModel
        let viewModel = timerViewModels[time] ?? TimerViewModel(time: time)
        TimerView(viewModel: viewModel)
          .onAppear {
            // 首次进入时保存ViewModel和启动任务
            if timerViewModels[time] == nil {
              timerViewModels[time] = viewModel
            }
            if timerTasks[time] == nil {
              let task = Task {
                await viewModel.start()
              }
              timerTasks[time] = task
            }
          }
      }
    }
    // 监听导航栈路径变化,处理任务的取消
    .onChange(of: path) { oldPath, newPath in
      let removedTimes = Set(oldPath).subtracting(newPath)
      for time in removedTimes {
        timerTasks[time]?.cancel()
        timerTasks.removeValue(forKey: time)
        // 可选:如果不需要保留历史计时器数据,可以移除ViewModel
        // timerViewModels.removeValue(forKey: time)
      }
    }
  }
}

class TimerViewModel: ObservableObject {
  @Published var time: Int
  
  init(time: Int) {
    self.time = time
  }
  
  @MainActor
  func start() async {
    repeat {
      do { try await Task.sleep(nanoseconds: 1_000_000_000) }
      catch { self.time = 0 }
      self.time += 1
    } while !Task.isCancelled
  }
}

struct TimerView: View {
  @ObservedObject var viewModel: TimerViewModel
  
  var body: some View {
    VStack {
      Text("Time is \(viewModel.time)")
      NavigationLink(value: viewModel.time + 1, label: { Text("Next") })
    }
  }
}

效果说明:

  • 当你导航到新的计时器页面时,原页面的计时器会继续运行,因为它的ViewModel和任务仍被父视图持有。
  • 当你从导航栈中返回(原页面被移除),对应的任务会被自动取消,避免资源浪费。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 23:19:53