在 SwiftUI 中使用 Timer 触发 NavigationView 更改的正确方法

Correct way to use a Timer to trigger NavigationView change in SwiftUI

我有一个应用程序,我希望用户能够在其中启动定时任务,当时间用完时,导航层次结构应该弹出并将用户带回来。我的代码可以正常工作,但我不喜欢代码的味道。这是处理此类问题的正确方法吗?

class SimpleTimerManager: ObservableObject {
  @Published var elapsedSeconds: Double = 0.0
  private(set) var timer = Timer()
  
  func start() {
    timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) {_ in
      self.elapsedSeconds += 0.01
    }
  }
  
  func stop() {
    timer.invalidate()
    elapsedSeconds = 0.0
  }
}


struct ContentView: View {
  @ObservedObject var timerManager = SimpleTimerManager()
  
  var body: some View {
    NavigationView {
      NavigationLink(destination: CountDownIntervalView(
        timerManager: timerManager, length: 5.0
      )) {
        Text("Start the timer!")
      }
    }
    .navigationViewStyle(StackNavigationViewStyle())
  }
}


struct CountDownIntervalView: View {
  @ObservedObject var timerManager: SimpleTimerManager
  var length: Double
  @Environment(\.presentationMode) var mode: Binding<PresentationMode>
  var interval: Double {
    let interval = length - self.timerManager.elapsedSeconds
    if interval <= 0 {
      self.mode.wrappedValue.dismiss()
      self.timerManager.stop()
    }
    return interval
  }
    
  var body: some View {
    VStack {
      Text("Time remaining: \(String(format: "%.2f", interval))")
      Button(action: {
        self.mode.wrappedValue.dismiss()
        self.timerManager.stop()
      }) {
        Text("Quit early!")
      }
    }
    .navigationBarBackButtonHidden(true)
    .onAppear(perform: {
      self.timerManager.start()
    })
  }
}

我有一个自定义后退按钮,我想保留它。我主要担心的是让代码停止计时器并在计算的 属性 中弹出导航感觉不对。我更喜欢

Text("\(interval)").onReceive(timerManager.timer, perform: { _ in
  if self.interval <= 0 {
    self.mode.wrappedValue.dismiss()
    self.timerManager.stop()
  }
})

CountDownIntervalView 中,但这会产生编译器错误 - Unable to infer complex closure return type; add explicit type to disambiguate - 而且,老实说,我不确定 方法是否有意义要么(将有条件地弹出导航的代码附加到 UI 的一块)。解决此问题的“最佳实践”方法是什么?

感谢任何想法。

这是一个解决方案。使用 Xcode 11.4 / iOS 13.4

测试
struct CountDownIntervalView: View {
  @ObservedObject var timerManager: SimpleTimerManager
  var length: Double
  @Environment(\.presentationMode) var mode: Binding<PresentationMode>

  var interval: Double {
    length - self.timerManager.elapsedSeconds
  }

  var body: some View {
    VStack {
      Text("Time remaining: \(String(format: "%.2f", interval))")
        .onReceive(timerManager.$elapsedSeconds) { _ in
            if self.interval <= 0 {
              self.mode.wrappedValue.dismiss()
              self.timerManager.stop()
            }
        }

    // ... other your code