SwiftUI: How do you restart a timer after cancelling it?

前端 未结 3 2031
情话喂你
情话喂你 2021-02-15 17:01

I have a navigation view consisting of the main ContentView and a TimerView. The TimerView has a timer which correctly increments and also corr

相关标签:
3条回答
  • 2021-02-15 17:52

    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

    • If its transient (ie: resets when the view is removed from screen and reloaded) then put the timer in a @State (be aware of strong retention with self captures - again documentation is thin on how onReceive stores its connections)
    • If its non-transient (ie: preserved over the view loading/unloading) then it needs to go into an external model (or a global var) and brought in via the init call for TimerView

    That you have @State var secondsElapsed makes me think it should be transient

    0 讨论(0)
  • 2021-02-15 17:54

    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

    0 讨论(0)
  • 2021-02-15 17:58

    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
        }
    }
    
    0 讨论(0)
提交回复
热议问题