62

How can I make a countdown with an NSTimer using Swift?

Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
Giovanie Rodz
  • 1,741
  • 3
  • 12
  • 7
  • Be aware, that `NSTimer` doesn't have enough presision: countdown might not match seconds exactly. What I mean is you might expect timer to trigger at `1, 2, 3...` seconds, but it will trigger at `0.998, 1.899, 2.889...` seconds (for example). If you need high presion, I'd suggest checking this approach: https://stackoverflow.com/a/3519913/1226304 – derpoliuk Feb 24 '23 at 12:19

21 Answers21

72

In Swift 5.1 this will work:

var counter = 30

override func viewDidLoad() {
    super.viewDidLoad()

    Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)
}



@objc func updateCounter() {
    //example functionality
    if counter > 0 {
        print("\(counter) seconds to the end of the world")
        counter -= 1
    }
}
Ben Morgan
  • 45
  • 1
  • 1
  • 7
71

Question 1:

@IBOutlet var countDownLabel: UILabel!

var count = 10

override func viewDidLoad() {
    super.viewDidLoad()

    var timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(UIMenuController.update), userInfo: nil, repeats: true)
}

func update() {
    if(count > 0) {
        countDownLabel.text = String(count--)
    }
}

Question 2:

You can do both. SpriteKit is the SDK you use for scene, motion, etc. Simple View Application is the project template. They should not conflict

Krunal Nagvadia
  • 1,083
  • 2
  • 12
  • 33
Bigman
  • 1,363
  • 11
  • 17
  • 5
    Consider using count-=1 instead of C like -- operator. I'd put String(count) and underneath, the count-=1. – Samarey Dec 29 '19 at 06:53
52

Swift 5 with Closure:

class ViewController: UIViewController {
    
var secondsRemaining = 30
    
@IBAction func startTimer(_ sender: UIButton) {
        
    Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (Timer) in
        if self.secondsRemaining > 0 {
            print ("\(self.secondsRemaining) seconds")
            self.secondsRemaining -= 1
        } else {
            Timer.invalidate()
        }
    }
            
}
Michael Shulman
  • 643
  • 5
  • 6
  • how you will invalidate the timer? – Rana Ali Waseem Mar 16 '20 at 16:27
  • 1
    I would suggest replacing self.invalidate() with *Timer.invalidate()* since the use of self shows an unresolved identifier – V P Jun 16 '20 at 19:40
  • 2
    From what I can see here, this will cause a retain cycle (I'm about 95% sure - no tests carried out). You need to be capturing self weakly to reference it from within the timer closure. You've also cased the name `Timer` with an uppercase T, making it look like a type, so `Timer.invalidate()` reads as a static function. This should be altered like so `{ [weak self] (timer) in` to both capture weak self and to make it clear that `timer` is the instance of the timer. – mylogon Sep 21 '20 at 13:42
37

Swift 4.1 and Swift 5. The updatetime method will called after every second and seconds will display on UIlabel.

     var timer: Timer?
     var totalTime = 60
    
     private func startOtpTimer() {
            self.totalTime = 60
            self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
        }
    
    @objc func updateTimer() {
            print(self.totalTime)
            self.lblTimer.text = self.timeFormatted(self.totalTime) // will show timer
            if totalTime != 0 {
                totalTime -= 1  // decrease counter timer
            } else {
                if let timer = self.timer { 
                    timer.invalidate()
                    self.timer = nil
                }
            }
        }
    func timeFormatted(_ totalSeconds: Int) -> String {
        let seconds: Int = totalSeconds % 60
        let minutes: Int = (totalSeconds / 60) % 60
        return String(format: "%02d:%02d", minutes, seconds)
    }
Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
  • 1
    whenever we add self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) Here, timeInterval = 1.0 so basically to show timer value it will take 1 second so to avoid that, like timer should run immediately when it's fire is there any workaround I should do? tried making timeInterval = 0.1 but which is not good idea as timer function will call every 0.1 seconds. – Sagar Daundkar Oct 20 '20 at 06:50
8

Variable for your timer

var timer = 60

NSTimer with 1.0 as interval

var clock = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "countdown", userInfo: nil, repeats: true)

Here you can decrease the timer

func countdown() {
    timer--
}
Henny Lee
  • 2,970
  • 3
  • 20
  • 37
5

Swift 3

private let NUMBER_COUNT_DOWN   = 3

var countDownLabel = UILabel()
var countDown = NUMBER_COUNT_DOWN
var timer:Timer?


private func countDown(time: Double)
{
    countDownLabel.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
    countDownLabel.font = UIFont.systemFont(ofSize: 300)
    countDownLabel.textColor = .black
    countDownLabel.center = CGPoint(x: self.view.frame.width / 2, y: self.view.frame.height / 2)

    countDownLabel.textAlignment = .center
    self.view.addSubview(countDownLabel)
    view.bringSubview(toFront: countDownLabel)

    timer = Timer.scheduledTimer(timeInterval: time, target: self, selector: #selector(updateCountDown), userInfo: nil, repeats: true)
}

func updateCountDown() {
    if(countDown > 0) {
        countDownLabel.text = String(countDown)
        countDown = countDown - 1
    } else {
        removeCountDownLable()
    }
}

private func removeCountDownLable() {
    countDown = NUMBER_COUNT_DOWN
    countDownLabel.text = ""
    countDownLabel.removeFromSuperview()

    timer?.invalidate()
    timer = nil
}
Giang
  • 3,553
  • 30
  • 28
5

For Egg Countdown Timer.

class ViewController: UIViewController {
    var secondsRemaining = 60
    var eggCountdown = 0
    let eggTimes = ["Soft": 5, "Medium": 7,"Hard": 12]
    
    
    
    @IBAction func hardnessSelected(_ sender: UIButton) {
        let hardness = sender.currentTitle!
        let result = eggTimes[hardness]!
        eggCountdown = result * secondsRemaining
        
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (Timer) in
                if self.eggCountdown > 0 {
                    print ("\(self.eggCountdown) seconds.")
                    self.eggCountdown -= 1
                } else {
                    Timer.invalidate()
                }
            }
        
    }
    
}
Hau
  • 51
  • 1
  • 2
4

Swift 5 another way. Resistant to interaction with UI

I would like to show a solution that is resistant to user interaction with other UI elements during countdown. In the comments I explained what each line of code means.

 var timeToSet = 0
 var timer: Timer?

 ...

 @IBAction func btnWasPressed(_ sender: UIButton) {

      //Setting the countdown time
      timeLeft = timeToSet
      //Disabling any previous timers.
      timer?.invalidate()
      //Initialization of the Timer with interval every 1 second with the function call.
      timer = Timer(timeInterval: 1.0, target: self, selector: #selector(countDown), userInfo: nil, repeats: true)
      //Adding Timer to the current loop
      RunLoop.current.add(timer!, forMode: .common)

  }

 ...

 @objc func countDown() {

     if timeLeft > 0 {
         print(timeLeft)
         timeLeft -= 1
     } else {
         // Timer stopping
         timer?.invalidate()
     }
 }
TomAshTee
  • 249
  • 2
  • 5
4

For use in Playground for fellow newbies, in Swift 5, Xcode 11:

Import UIKit

var secondsRemaining = 10
    
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (Timer) in
    if secondsRemaining > 0 {
        print ("\(secondsRemaining) seconds")
        secondsRemaining -= 1
    } else {
        Timer.invalidate()
    }
}
3

XCode 10 with Swift 4.2

import UIKit

class ViewController: UIViewController {

   var timer = Timer()
   var totalSecond = 10

   override func viewDidLoad() {
       super.viewDidLoad()
       startTimer()
   }


   func startTimer() {
       timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
   }

   @objc func updateTime() {

        print(timeFormatted(totalSecond))

        if totalSecond != 0 {
           totalSecond -= 1
        } else {
           endTimer()
        }
    }

    func endTimer() {
        timer.invalidate()
    }

    func timeFormatted(_ totalSeconds: Int) -> String {
        let seconds: Int = totalSeconds % 60
        return String(format: "0:%02d", seconds)
    }

}
wsnjy
  • 174
  • 1
  • 7
2

Swift 4

Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.updateTime), userInfo: nil, repeats: true)

Update function

@objc func updateTime(){
    debugPrint("jalan")
}
luhuiya
  • 2,129
  • 21
  • 20
2

Swift4

    @IBOutlet weak var actionButton: UIButton!
    @IBOutlet weak var timeLabel: UILabel!
    var timer:Timer?
    var timeLeft = 60

override func viewDidLoad() {
    super.viewDidLoad()
    setupTimer()
}

func setupTimer() {
    timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(onTimerFires), userInfo: nil, repeats: true)
}

@objc func onTimerFires() {
    timeLeft -= 1
    timeLabel.text = "\(timeLeft) seconds left"

    if timeLeft <= 0 {
        actionButton.isEnabled = true
        actionButton.setTitle("enabled", for: .normal)
        timer?.invalidate()
        timer = nil
    }
}

@IBAction func btnClicked(_ sender: UIButton) {
    print("API Fired")
}
Devesh.452
  • 893
  • 9
  • 10
2

this for the now swift 5.0 and newst

var secondsRemaining = 60


Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)
}
@objc func updateCounter(){
    if secondsRemaining > 0 {
    print("\(secondsRemaining) seconds.")
    secondsRemaining -= 1
            }
        }
dbc
  • 104,963
  • 20
  • 228
  • 340
2

this is an egg timer.

import UIKit

class ViewController: UIViewController {
    let eggTimes = ["Soft": 0.1, "Medium": 2, "Hard": 3]
    
    var eggTime = 0
    
    var timer = Timer()
    
    @IBOutlet weak var label: UILabel!
    
    @IBAction func b(_ sender: UIButton) {
    
    timer.invalidate()
    
    let hardness = sender.currentTitle!
    eggTime = Int(eggTimes[hardness]! * 60)
    timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(update), userInfo: nil, repeats: true)
        }
    @objc func update() {
        if (eggTime > 0) {
            print("\(eggTime) seconds")
            eggTime -= 1
            }
        if (eggTime == 0){
            label.text = ("done")
        }
    }
}
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
1

Make Countdown app Xcode 8.1, Swift 3

import UIKit
import Foundation

class ViewController: UIViewController, UITextFieldDelegate {

    var timerCount = 0
    var timerRunning = false

    @IBOutlet weak var timerLabel: UILabel! //ADD Label
    @IBOutlet weak var textField: UITextField! //Add TextField /Enter any number to Countdown

    override func viewDidLoad() {
        super.viewDidLoad()

        //Reset
        timerLabel.text = ""
        if timerCount == 0 {
            timerRunning = false
        }
}

       //Figure out Count method
    func Counting() {
        if timerCount > 0 {
        timerLabel.text = "\(timerCount)"
            timerCount -= 1
        } else {
            timerLabel.text = "GO!"

        }

    }

    //ADD Action Button
    @IBAction func startButton(sender: UIButton) {

        //Figure out timer
        if timerRunning == false {
         _ = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.Counting), userInfo: nil, repeats: true)
            timerRunning = true
        }

        //unwrap textField and Display result
        if let countebleNumber = Int(textField.text!) {
            timerCount = countebleNumber
            textField.text = "" //Clean Up TextField
        } else {
            timerCount = 3 //Defoult Number to Countdown if TextField is nil
            textField.text = "" //Clean Up TextField
        }

    }

    //Dismiss keyboard
    func keyboardDismiss() {
        textField.resignFirstResponder()
    }

    //ADD Gesture Recignizer to Dismiss keyboard then view tapped
    @IBAction func viewTapped(_ sender: AnyObject) {
        keyboardDismiss()
    }

    //Dismiss keyboard using Return Key (Done) Button
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        keyboardDismiss()

        return true
    }

}

https://github.com/nikae/CountDown-

NikaE
  • 614
  • 1
  • 8
  • 15
1

Add this to the end of your code... and call startTimer function with a parameter of where you want to count down to... For example (2 hours to the future of the date right now) -> startTimer(for: Date().addingTimeInterval(60*60*2))

Click here to view a screenshot of iPhone Simulator of how it'll look

extension ViewController
{
    func startTimer(for theDate: String)
    {
        let todaysDate = Date()
        let tripDate = Helper.getTripDate(forDate: theDate)
        let diffComponents = Calendar.current.dateComponents([.hour, .minute], from: Date(), to: tripDate)
        if let hours = diffComponents.hour
        {
            hoursLeft = hours
        }
        if let minutes = diffComponents.minute
        {
            minutesLeft = minutes
        }
        if tripDate > todaysDate
        {
            timer = Timer.scheduledTimer(timeInterval: 1.00, target: self, selector: #selector(onTimerFires), userInfo: nil, repeats: true)
        }
        else
        {
            timerLabel.text = "00:00:00"
        }
    }
    
    @objc func onTimerFires()
    {
        secondsLeft -= 1
        
        //timerLabel.text = "\(hoursLeft):\(minutesLeft):\(secondsLeft)"
        timerLabel.text = String(format: "%02d:%02d:%02d", hoursLeft, minutesLeft, secondsLeft)
        if secondsLeft <= 0 {
            if minutesLeft != 0
            {
                secondsLeft = 59
                minutesLeft -= 1
            }
        }
        
        if minutesLeft <= 0 {
            if hoursLeft != 0
            {
                minutesLeft = 59
                hoursLeft -= 1
            }
        }
        
        if(hoursLeft == 0 && minutesLeft == 0 && secondsLeft == 0)
        {
            timer.invalidate()
        }
        
    }
}
Sarvesh Sridhar
  • 387
  • 3
  • 3
1
import UIKit

class ViewController: UIViewController {

    let eggTimes = ["Soft": 300, "Medium": 420, "Hard": 720]
    
    var secondsRemaining = 60

    @IBAction func hardnessSelected(_ sender: UIButton) {
        let hardness = sender.currentTitle!

        secondsRemaining = eggTimes[hardness]!

        Timer.scheduledTimer(timeInterval: 1.0, target: self, selector:
            #selector(UIMenuController.update), userInfo: nil, repeats: true)
    }
    @objc func countDown() {
         if secondsRemaining > 0 {
             print("\(secondsRemaining) seconds.")
            secondsRemaining -= 1
        
         }
    }
}
fcdt
  • 2,371
  • 5
  • 14
  • 26
Natallyson
  • 41
  • 3
  • Welcome to Stack Overflow. When answering an old question having an accepted answer (look for the green ✓) as well as other answers ensure your answer adds something new or is otherwise helpful in relation to them. (What's the reason for your answer? Are other answers not sufficient, wrong, outdated, ...?) Also while this code snippet may solve the problem, it doesn't explain why or how it answers the question. Please consider to [include an explanation for your code](https://meta.stackoverflow.com/q/392712/5698098), as that helps to improve the quality of your post. – Ivo Mori Sep 23 '20 at 01:19
  • As you're starting out here, please take the [tour](https://stackoverflow.com/tour) to learn how Stack Overflow works and also have a look at the [contribution guideline](https://stackoverflow.com/help/how-to-answer). – Ivo Mori Sep 23 '20 at 01:20
1

You really shouldn’t. Grand Central Dispatch is much more reliable.

Matt
  • 560
  • 1
  • 5
  • 12
0

Timer with Combine

var counter = 30
    
let cancellable = Timer.publish(every: 1, on: .main, in: .default).autoconnect().sink(receiveValue: { _ in
    counter = counter > 0 ? counter - 1 : 0
    print("timer: ", counter)
})
zouritre
  • 231
  • 3
  • 6
-1

import UIKit import AVFoundation

class ViewController: UIViewController {

@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var progressBar: UIProgressView!

let eggTime = ["Soft":3, "Medium":4, "Hard":7]
var timer = Timer()
var totalTime = 0
var secondsPassed = 0
var player: AVAudioPlayer!

@IBAction func hardnessSelected(_ sender: UIButton) {

let hardness = sender.currentTitle!
       totalTime = eggTime[hardness]!
       secondsPassed = 0
       progressBar.progress = 0.0
       titleLabel.text = hardness

timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [unowned self] timer in

  if self.secondsPassed < self.totalTime {
                self.secondsPassed += 1
  let progress = Float(self.secondsPassed) /Float(self.totalTime)
                self.progressBar.progress = progress
                print (progress)
            } else {
                 self.timer.invalidate()
                 self.titleLabel.text = "DONE"
Angie
  • 1
  • 1
-1
//MARK:-  IBOutlets 
@IBOutlet weak var verificationTimeVal: NSTextField!

//MARK:-  Declarations 
var secondsRemaining = 5 * 60 //5 minutes
var myTimer : Timer?

override func viewDidAppear() {
    super.viewDidAppear()
    DispatchQueue.main.async {
        //Call the function to start the timer
        self.controlTimer()
    }
}

func controlTimer() {
    myTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
        if self?.secondsRemaining ?? 0 > 0 {
            let minutes = Int(self?.secondsRemaining ?? 0) / 60
            let seconds = Int(self?.secondsRemaining ?? 0) % 60
            //VerificationTimeVal is a UI element to display the time
            let timerResults = String(format: "%02d:%02d", minutes, seconds)
            self?.verificationTimeVal.stringValue = "\(timerResults) Minutes"
            self?.secondsRemaining -= 1
        } else {
            timer.invalidate()
            //VerificationTimeVal is a UI element to display the time
            self?.verificationTimeVal.stringValue = "00:00 Minutes"
        }
    }
    
    // Add the timer to the current RunLoop
    RunLoop.current.add(myTimer!, forMode: .common)
}
Mannam Brahmam
  • 2,225
  • 2
  • 24
  • 36