有没有办法在特定屏幕是导航堆栈时保持使用
.task
修改器启动的任务处于活动状态?
例如,在下面的计时器示例中,一旦您导航到另一个计时器,计时器就会停止更新。
struct TimerList: View {
@State private var times = [0, 1, 2, 3, 4, 5]
var body: some View {
NavigationStack {
List(times, id: \.self) { time in
NavigationLink(value: time) {
Text("\(time)")
}
}
.navigationDestination(for: Int.self) { time in
TimerView(time: 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 {
let time: Int
@StateObject var viewModel: TimerViewModel
init(time: Int) {
self.time = time
_viewModel = StateObject(wrappedValue: TimerViewModel(time: time))
}
var body: some View {
VStack {
Text("Time is \(viewModel.time)")
NavigationLink(value: time + 1, label: { Text("Next") })
}
.task { await viewModel.start() }
}
}
有没有办法将任务范围限制在屏幕上,以便它们在导航堆栈中继续运行,但一旦被删除就被取消?
尝试下面的代码。
class NavigationManager: ObservableObject {
@Published var path = NavigationPath()
func popToRoot() {
path.removeLast(path.count) // Clears the navigation path
}
func push<T: Hashable>(_ item: T) {
path.append(item) // Adds an item to the navigation path
}
}
struct TimerList: View {
@State private var times = [0, 1, 2, 3, 4, 5]
@StateObject private var navigationManager = NavigationManager()
@StateObject var viewModel = TimerViewModel()
var body: some View {
NavigationStack(path: $navigationManager.path) {
List(times, id: \.self) { time in
Button(action: {
viewModel.time = time
viewModel.start()
navigationManager.push(time)
}) {
Text("\(time)")
}
}
.onAppear {
viewModel.stop()
}
.navigationDestination(for: Int.self) { time in
TimerView()
}
}
.environmentObject(viewModel)
}
}
class TimerViewModel: ObservableObject {
@Published var time: Int = 0
private var task: Task<Void, Never>? = nil
@MainActor
func start() {
task = Task { [weak self] in
guard let self = self else { return }
do {
while !Task.isCancelled {
try await Task.sleep(nanoseconds: 1_000_000_000)
await MainActor.run {
self.time += 1
print("Timer: \(self.time)")
}
}
} catch {
print("Timer task cancelled or encountered an error.")
}
}
}
func stop() {
task?.cancel()
task = nil
}
}
struct TimerView: View {
@EnvironmentObject var viewModel: TimerViewModel
var body: some View {
VStack {
Text("Time is \(viewModel.time)")
NavigationLink(value: viewModel.time, label: { Text("Next") })
}
}
}