Search code examples
swifttimer

Why does my timer in swift keep speeding up?


I am creating a trivia app in swift and I have a timer that counts down each question. However as the user progresses with each question the timer speeds up. Can someone help me fix this?

My runGameTimer function:

func runGameTimer()
{
    gameTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(RockTriviaViewController.updateGameTimer), userInfo: nil, repeats: true)

}

My updateGameTimer function:

@objc func updateGameTimer()
{
    gameInt -= 1
    timerLabel.text = String(gameInt)
    if (gameInt == 0)
    {
        gameTimer.invalidate()
      /*
        if (currentQuestion != rockQuestions[questionSet].count)
        {
            newQuestion()
        }
        else
        {
            performSegue(withIdentifier: "showRockScore", sender: self)
        }
       */
    }
}

Where I call my code:

func newQuestion()
{
    gameInt = 11
    runGameTimer()
    rockQuestion.text = rockQuestions[questionSet][currentQuestion]
    rightAnswerPlacement = arc4random_uniform(3)+1
    var Button: UIButton = UIButton()
    var x = 1

    for i in 1...3
    {
        Button = view.viewWithTag(i) as! UIButton
        if(i == Int(rightAnswerPlacement))
        {
            Button.setTitle(rockAnswers[questionSet][currentQuestion][0], for: .normal)
        }
        else
        {
            Button.setTitle(rockAnswers[questionSet][currentQuestion][x], for: .normal)
            x = 2
        }
    }

    currentQuestion += 1
}

Solution

  • You're calling runGameTimer() in every call to newQuestion(). If a timer was already running then you'll add a new timer each time, and they will all call your selector. So if you have 3 timers running, your selector will be called 3x as often. That's not what you want.

    Change your timer variable to be weak:

    weak var gameTimer: Timer?
    

    And then in runGameTimer invalidate the timer before creating a new one, using optional chaining:

    func runGameTimer() {
        gameTimer?.invalidate() //This will do nothing if gameTimer is nil.
                                //it will also cause the gameTimer to be nil since it's weak.
    
        gameTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(RockTriviaViewController.updateGameTimer), userInfo: nil, repeats: true)
    }
    

    By making the game timer weak it will get set to nil as soon as it's invalidated. (When you schedule a timer the system retains it while it is running so it stays valid as long as it continues to run.)

    By using optional chaining to reference the timer:

    gameTimer?.invalidate()
    

    The code doesn't do anything if gameTimer is nil.