Search code examples
swiftuibuttonstoryboardswift5dispatch-async

Bug possibly related to DispatchQueue.main.asyncAfter in UIButton action when button is spammed/rapidly and repeatedly pressed


The project can be cloned here https://github.com/randomjuniorburger/quizapp

I have a UIButton which is linked to an action which makes use of DispatchQueue.main.asyncAfter. The app works well except when the user spams the True/False buttons/presses or triggers either of them many times consecutively in a short interval, causing the delay(s) to overlap and allowing the user to achieve a higher score than should be possible (e.g. only 8 questions have the answer "True", but a score of 10 is possible due to the bug if the true button is spammed)

I am wondering how I can fix/prevent this?

NB - I am a beginner/hobbyist Swift developer. This project is my attempt to recreate a module of a course I am taking by Angela Yu.

I welcome any suggestions as to how my code can be improved and more closely follow best-practice.

The code for the button alone is;

@IBAction func answerPressed(_ sender: UIButton) {

    let userAnswer = sender.currentTitle
    let actualAnswer = quizArray[questionNumber].answer

    if questionNumber < (quizArray.count - 1) {
        self.questionNumber += 1
        updateProgressBar()
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
            self.questionLabel.text = self.quizArray[self.questionNumber].text
        }

        if userAnswer == actualAnswer {
            questionLabel.text = "✅"
            score += 1
            scoreLabel.text = String("Score: \(score) / 12")

        } else {
            questionLabel.text = "❌"
        }

    } else if questionNumber == (quizArray.count - 1) && questionLabel.text != "End of Quiz" {
        if userAnswer == actualAnswer {
            questionLabel.text = "✅"
            score += 1
            scoreLabel.text = String("Score: \(score) / 12")
        } else {
            questionLabel.text = "❌"
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
            self.progressBar.progress = Float(1)
            print("No more questions")
            self.questionLabel.text = "End of Quiz"
        }
    }

}

Solution

  • Here is possible solution - just don't give possibility for a user to generate not desired taps

    sender.isEnabled = false // don't allow action until question updated
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
        self.questionLabel.text = self.quizArray[self.questionNumber].text
        sender.isEnabled = true // << allow user interaction
    }
    

    in second do the same