Marco Cappai
Marco Cappai

Reputation: 115

Swift creation of separate timers

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

Answers (4)

vacawama
vacawama

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

superpuccio
superpuccio

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

Ahmad F
Ahmad F

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

10623169
10623169

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

Related Questions