SwiftUI NavigationView with PresentationMode creates bug in multilevel navigation hierarchy

I asked this question on StackOverflow also, but my interactions there have lead me to think this might be a SwiftUI bug, so I thought I'd ask here too.

I have an iOS 13.5 SwiftUI (macOS 10.15.6) app that requires the user to navigate two levels deep in a NavigationView hierarchy to play a game. The game is timed. I'd like to use custom back buttons in both levels, but if I do, the timer in the second level breaks in a strange way. If I give up on custom back buttons in the first level and use the system back button everything works. Here is a minimum app that replicates the problem:

Code Block
class SimpleTimerManager: ObservableObject {
@Published var elapsedSeconds: Double = 0.0
private(set) var timer = Timer()
func start() {
print("timer started")
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) {_ in
if (Int(self.elapsedSeconds * 100) % 100 == 0) { print ("\(self.elapsedSeconds)") }
self.elapsedSeconds += 0.01
}
}
func stop() {
timer.invalidate()
elapsedSeconds = 0.0
print("timer stopped")
}
}
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: CountDownIntervalPassThroughView()) {
Text("Start the timer!")
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct CountDownIntervalPassThroughView: View {
@Environment(\.presentationMode) var mode: Binding<PresentationMode>
var body: some View {
VStack {
NavigationLink(destination: CountDownIntervalView()) {
Text("One more click...")
}
Button(action: {
self.mode.wrappedValue.dismiss()
print("Going back from CountDownIntervalPassThroughView")
}) {
Text("Go back!")
}
}
.navigationBarBackButtonHidden(true)
}
}
struct CountDownIntervalView: View {
@ObservedObject var timerManager = SimpleTimerManager()
@Environment(\.presentationMode) var mode: Binding<PresentationMode>
var interval: Double { 10.0 - self.timerManager.elapsedSeconds }
var body: some View {
VStack {
Text("Time remaining: \(String(format: "%.2f", interval))")
.onReceive(timerManager.$elapsedSeconds) { _ in
print("\(self.interval)")
if self.interval <= 0 {
print("timer auto stop")
self.timerManager.stop()
self.mode.wrappedValue.dismiss()
}
}
Button(action: {
print("timer manual stop")
self.timerManager.stop()
self.mode.wrappedValue.dismiss()
}) {
Text("Quit early!")
}
}
.onAppear(perform: {
self.timerManager.start()
})
.navigationBarBackButtonHidden(true)
}
}


Poking at this some more, I see two strange behaviors. If I don't use a pass through, everything works for manual clicking. But if I let the timer expire and create a pop back, when I try to restart it, the view immediately pops, but the timer keeps running. If I do use a pass through, the timer starts when I navigate two levels down, but the view doesn't update. I wonder if this is a bug in how SwiftUI is handling the onAppear and mode.dismiss methods.
SwiftUI NavigationView with PresentationMode creates bug in multilevel navigation hierarchy
 
 
Q