Reputation: 115
I am trying to make a countdown timer that connects to a button, and am currently using the
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.clock), userInfo: nil, repeats: true)
However, I want it so if I press the same button a new timer starts with the similar settings, so in the console there are 2, 3, etc. countdown timers going at the same time. How do I make it so whenever I press the button a new timer generates with same settings as the previous but the old one is still active and ticking down?
Upvotes: 4
Views: 1358
Reputation: 154593
To keep track of the various timers, you want to create an array of timers ([Timer]
) and create a TimerState
to pass into each timer as the userInfo
object. Then when updateTimer()
is called, you can access the state
for that timer as timer.userInfo
and use it. When a timer reaches 0
, call invalidate()
and remove it from the list of timers.
Uses the timers
array to stop all of the active timers if the user presses the Stop All Timers button.
import UIKit
class TimerState {
let number: Int
var count: Int
init(number: Int, count: Int) {
self.number = number
self.count = count
}
}
class ViewController: UIViewController {
var timerNumber = 1
var startingCount = 10
// Array to hold active timers so that all can be stopped
var timers = [Timer]()
@IBAction func startTimer(_ sender: UIButton) {
let state = TimerState(number: timerNumber, count: startingCount)
let timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer), userInfo: state, repeats: true)
timers.append(timer)
timerNumber += 1
}
@objc func updateTimer(_ timer: Timer) {
guard let state = timer.userInfo as? TimerState else { return }
state.count -= 1
if state.count == 0 {
print("Timer \(state.number) is done")
timer.invalidate()
// remove this timer from the list of active timers
if let index = timers.firstIndex(of: timer) {
timers.remove(at: index)
}
} else {
print("Timer \(state.number): \(state.count)")
}
}
@IBAction func stopAllTimers(_ sender: UIButton) {
for timer in timers {
guard let state = timer.userInfo as? TimerState else { continue }
print("Timer \(state.number) stopped")
timer.invalidate()
}
}
}
Pressing the Start Timer button 3 times yields the following output in the console:
Timer 1: 9 Timer 1: 8 Timer 1: 7 Timer 2: 9 Timer 1: 6 Timer 2: 8 Timer 1: 5 Timer 2: 7 Timer 3: 9 Timer 1: 4 Timer 2: 6 Timer 3: 8 Timer 1: 3 Timer 2: 5 Timer 3: 7 Timer 1: 2 Timer 2: 4 Timer 3: 6 Timer 1: 1 Timer 2: 3 Timer 3: 5 Timer 1 is done Timer 2: 2 Timer 3: 4 Timer 2: 1 Timer 3: 3 Timer 2 is done Timer 3: 2 Timer 3: 1 Timer 3 is done
Upvotes: 4
Reputation: 12972
Well, it depends on whether you need to track all of those timers or not. If not is pretty straightforward. Just create a timer each time you click on the button:
func buttonDidClick() {
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.clock), userInfo: nil, repeats: true)
}
if you need to keep track of those timers use an Array:
func buttonDidClick() {
myArray.append(Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.clock), userInfo: nil, repeats: true))
}
This way you can even cancel all the timers or do anything you need.
Upvotes: 0
Reputation: 31645
Based on your code:
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.clock), userInfo: nil, repeats: true)
I would assume that you are declaring timer
as an instance variable in ViewController
something like:
class ViewController: UIViewController {
var timer: Timer?
@IBAction func tapped(_ sender: Any) {
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.clock), userInfo: nil, repeats: true)
}
@objc func clock() {
print("counting...")
}
}
So each tap on the button, it will schedule a new timing session even if you are using the same Timer
instance. In this case, what you should do is to invalidate timer
each time before assigning Timer.scheduledTimer
to it:
Stops the timer from ever firing again and requests its removal from its run loop.
In the button action method, add timer?.invalidate()
in the first:
@IBAction func tapped(_ sender: Any) {
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.clock), userInfo: nil, repeats: true)
}
Upvotes: 1
Reputation: 1044
Create a new Timer()
instance for each button press (if you do indeed want multiple timers) with the same properties.
Would be worth keeping a list of all of your Timers though so you can deal with them when you no longer need them (so you don't have many tens of timers building up if someone spammed the button).
Upvotes: 0