Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

ios

swift

swiftui

I have a navigation view consisting of the main ContentView and a TimerView. The TimerView has a timer which correctly increments and also correctly stops when I call self.timer.upstream.connect().cancel().

However when I go back to ContentView and then navigate to TimerView again, I want the timer to start counting again however this does not happen. secondsElapsed does reset to 0 but the timer doesn't run.

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: TimerView()) {
                Text("Go to Timer View")
            }
        }
    }
}

struct TimerView: View {
    @State var secondsElapsed = 0
    var timer = Timer.publish (every: 1, on: .main, in: .common).autoconnect()
    var body: some View {
        VStack {
            Text("\(self.secondsElapsed) seconds elapsed")
            Button("Stop timer",
                   action: {
                    self.timer.upstream.connect().cancel()
            })
        }.onReceive(timer) { _ in
            self.secondsElapsed += 1
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
like image 789
Jez Avatar asked Dec 09 '19 00:12

Jez


People also ask

How do I cancel a timer on Swiftui?

timer.upstream.connect().cancel()randomElement()! If we want to resume the timer, we will have to make some adjustments. Resuming timer requires reinitialization of the Timer so we will change our timer to a State property. fillColor = colors.

How do I start and stop a timer in Swiftui?

When you press the view, a timer should start counting upwards, and tapping again stops the timer. Finally, when you tap again to start the timer, the timer should begin at 0.

How do I use the timer in Swiftui?

If you want to run some code regularly, perhaps to make a countdown timer or similar, you should use Timer and the onReceive() modifier. It's important to use . main for the runloop option, because our timer will update the user interface.


1 Answers

To my knowledge (and historically), Timer cannot be restarted after cancellation or invalidation.

What I do is just re-declare the timer.

Update 12/2021:

The previous code was from SwiftUI 1, which had some "oddities," especially around lifecycle. So, I updated the code to how I would do it in SwiftUI 3.

For this code, you must import combine to declare a Cancellable type.

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)
    @State var connectedTimer: Cancellable? = nil
    
    var body: some View {
        VStack {
            Text("\(self.secondsElapsed) seconds elapsed")
            Button("Stop Timer", action: {
                self.cancelTimer()
            })
            Button("Continue Timer", action: {
                self.instantiateTimer()
            })
            Button("Restart Timer", action: {
                self.restartTimer()
            })
        }.onAppear {
            self.instantiateTimer()
        }.onDisappear {
            self.cancelTimer()
        }.onReceive(timer) { _ in
            self.secondsElapsed += 1
        }
    }
    
    func instantiateTimer() {
        self.timer = Timer.publish(every: 1, on: .main, in: .common)
        self.connectedTimer = self.timer.connect()
        return
    }
    
    func cancelTimer() {
        self.connectedTimer?.cancel()
        return
    }
    
    func resetCounter() {
        self.secondsElapsed = 0
        return
    }
    
    func restartTimer() {
        self.secondsElapsed = 0
        self.cancelTimer()
        self.instantiateTimer()
        return
    }
}
like image 88
Mykel Avatar answered Oct 04 '22 13:10

Mykel