I have a navigation view consisting of the main ContentView and a TimerView. The TimerView has a timer which correctly increments and also corr
Having a variable inside a View
which isn't attached to a @State
variable or an @ObservedObject
model doesn't seem to have very well defined documentation - but the general rule is that everything without a @State/@ObservedObject
annotation is essentially throwaway - especially as TimerView
is a struct
and won't be preserved over re-renders.
Timer.TimerPublisher
is a class - so essentially this would boil down to either two use cases
@State
(be aware of strong retention with self
captures - again documentation is thin on how onReceive
stores its connections) var
) and brought in via the init
call for TimerView
That you have @State var secondsElapsed
makes me think it should be transient
if you have a countdown timer, you define it something like this:
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
...and use it on a Text field for e.g.:
Text("Punkte: \(Punkte.formatnumber())")
.onReceive(timer) { input in
if time < 1 { gameOver() } else { self.time -= TimerToggle ? 1 : 0 }
}
so you only have to set the Toggle in the action of the button:
Button(action: { TimerToggle.toggle() }, label: {
Text("Pause")
})
Much simpler and works in SwiftUI 5.2
To my knowledge (and historically), Timer cannot be restarted after cancellation or invalidation.
What I do is just re-declare the timer.
Here is an example with some added personal preferences:
struct TimerView: View {
@State var secondsElapsed = 0
@State var timer: Timer.TimerPublisher = Timer.publish (every: 1, on: .main, in: .common)
var body: some View {
VStack {
Text("\(self.secondsElapsed) seconds elapsed")
Button("Stop timer",
action: {
self.cancelTimer() // Just to be a little more DRY/maintenance-friendly, but personal preference.
})
}.onAppear(perform: {
self.instantiateTimer() // You could also consider an optional self.timer variable.
self.timer.connect() // This allows you to manually connect where you want with greater efficiency, if you don't always want to autostart.
}).onDisappear(perform: {
// I don't know your exact flow, but you said you want the timer to restart upon return navigation.
// So, I am just assuming you want to stop the timer when you navigate out.
// But, you can also easily remove this closure.
// Also, this may not run as you would intuit.
self.cancelTimer()
}).onReceive(timer) { _ in
self.secondsElapsed += 1
}
}
func instantiateTimer() {
self.timer = Timer.publish (every: 1, on: .main, in: .common)
return
}
func cancelTimer() {
self.timer.connect().cancel()
return
}
}