如何让NavigationStack中页面的.task任务保持活跃直至页面移除?
原代码里的计时器在导航到新页面后停止,是因为SwiftUI的.task修饰符会在视图离开屏幕(被导航覆盖)时自动取消任务。要实现“页面在导航栈中就保持任务活跃,被移除时才取消”,需要把任务的管理权移到父视图,绑定导航栈的路径变化来控制任务的生命周期。
修改步骤:
父视图统一管理ViewModel和任务:
- 用字典存储每个计时器对应的
TimerViewModel和运行任务,避免页面被覆盖时ViewModel和任务被销毁。 - 通过监听导航栈路径的变化,在页面被从栈中移除时取消对应任务。
- 用字典存储每个计时器对应的
调整子视图的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
相关产品推荐
相关产品推荐

