Search code examples
iosswifttimerwaitswift5

Wait for Swift timers to finish


So I'm doing a simple game on swift 5 and I basically have a 3..2..1.. go timer to start the game and then a 3..2..1..stop timer to stop the game. And finally a function that displays the score. I need a way for each function call to wait for the timer to be done before the next one begins, any suggestions? Here's my code so far. (Also if you have any other suggestions on the app let me know as well, the end goal is to register how many taps of a button you can do in 3 seconds)

var seconds = 3 //Starting seconds
var countDownTimer = Timer()
var gameTimer = Timer()
var numberOfTaps = 0

override func viewDidLoad() {
    super.viewDidLoad()

    self.startCountdown(seconds: seconds)
    self.gameCountdown(seconds: seconds)
    self.displayFinalScore()


}

func startCountdown(seconds: Int) {
        countDownTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
            self?.seconds -= 1
            if self?.seconds == 0 {
                self?.countdownLabel.text = "Go!"
                timer.invalidate()
            } else if let seconds = self?.seconds {
                self?.countdownLabel.text = "\(seconds)"
            }
        }
}

func gameCountdown(seconds: Int) {
        gameTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
              self?.seconds -= 1
              if self?.seconds == 0 {
                  self?.countdownLabel.text = "Stop!"
                  timer.invalidate()
              } else if let seconds = self?.seconds {
                  self?.countdownLabel.text = "\(seconds)"
              }
          }
  }


    deinit {
        // ViewController going away.  Kill the timer.
        countDownTimer.invalidate()
    }




@IBOutlet weak var countdownLabel: UILabel!


@IBAction func tapMeButtonPressed(_ sender: UIButton) {
    if gameTimer.isValid {
        numberOfTaps += 1
    }
}

func displayFinalScore() {
    if !gameTimer.isValid && !countDownTimer.isValid {
        countdownLabel.text = "\(numberOfTaps)"
    }
}

Solution

  • You should think about the states your game could be in. It could be -

    • setup - Establish the game
    • starting - The first three seconds
    • running - After the first three seconds but before the end
    • ending - In the final three seconds
    • ended - Time is up.

    Each time your timer ticks you need to consider what action do you need to take and what state do you need to move to. You haven't said how long you want the game to last, but let's say it is 30 seconds.

    When a new game is started, you are in the setup state; The button is disabled (ie. it doesn't react to taps) and you set the score to 0. You move to the starting state. In the starting you show the countdown. After three seconds you enable the button and move into the running state. Once you reach 27 seconds, you move into the ending state and show the end count down Finally time is up and you move into the ended state, disable the button and show the score.

    You could code it something like this

    
    enum GameState {
    
        case setup
        case starting
        case running
        case ending
        case ended
    }
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var startButton: UIButton!
        @IBOutlet weak var tapButton: UIButton!
        @IBOutlet weak var countdownLabel: UILabel!
    
        var gameState = GameState.ended
        var gameTimer:Timer?
        var numberOfTaps = 0
        var gameStartTime = Date.distantPast
        let GAMEDURATION: TimeInterval = 30
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
        }
    
        @IBAction func startButtonTapped(_ sender: UIButton) {
            self.startGame()
        }
    
        @IBAction func tapMeButtonPressed(_ sender: UIButton) {
            self.numberOfTaps += 1
        }
    
        func startGame() {
            self.gameState = .setup
    
            self.gameTimer = Timer.scheduledTimer(withTimeInterval:0.1, repeats: true) { timer in
    
                let elapsedTime = -self.gameStartTime.timeIntervalSinceNow
                let timeRemaining = self.GAMEDURATION-elapsedTime
                switch self.gameState {
                case .setup:
                    self.gameStartTime = Date()
                    self.tapButton.isEnabled = false
                    self.startButton.isEnabled = false
                    self.numberOfTaps = 0
                    self.gameState = .starting
                case .starting:
                    if elapsedTime > 2.5 {
                        self.gameState = .running
                        self.tapButton.isEnabled = true
                        self.countdownLabel.text  = "Go!"
                    } else {
                        let countdown = Int(3-round(elapsedTime))
                        self.countdownLabel.text = "\(countdown)"
                    }
                case .running:
                    if timeRemaining < 4 {
                        self.gameState = .ending
                    }
                case .ending:
                    let countdown = Int(timeRemaining)
                    self.countdownLabel.text = "\(countdown)"
                    if timeRemaining < 1 {
                        self.countdownLabel.text = "Stop"
                        self.gameState = .ended
                        self.tapButton.isEnabled = false
                    }
                case .ended:
                    if timeRemaining <= 0 {
                        self.countdownLabel.text = "You tapped the button \(self.numberOfTaps) times"
                        self.startButton.isEnabled = true
                        self.gameTimer?.invalidate()
                        self.gameTimer = nil
                    }
                }
            }
        }
    }