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

- 55,884
- 29
- 169
- 223

- 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 Answers
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
}
}

- 45
- 1
- 1
- 7
-
2Great! Worked for me :) I just added timer invalidation to avoid issues when timer is called numerous times. – marika.daboja Mar 20 '20 at 05:23
-
I want the same thing, timer should be called several times. how did you create it? can you share please – Ulugbek Mar 24 '21 at 03:41
-
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

- 1,083
- 2
- 12
- 33

- 1,363
- 11
- 17
-
5Consider 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
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()
}
}
}

- 643
- 5
- 6
-
-
1I 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
-
2From 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
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)
}

- 9,221
- 1
- 66
- 58
-
1whenever 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
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--
}

- 2,970
- 3
- 20
- 37
-
1
-
1Cannot find operator '--' in scope; did you mean '-= 1'?. We should not use " timer--", instead use "counter -= 1" – Mahesh Nov 20 '20 at 11:12
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
}

- 3,553
- 30
- 28
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()
}
}
}
}

- 51
- 1
- 2
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()
}
}

- 249
- 2
- 5
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()
}
}

- 45
- 3
-
1The original question was about NSTimer; it would be helpful to explain the relationship between the two in your answer. – shader Jul 14 '20 at 20:19
-
2Hi @shader the NSTimer class is now changed to Timer, pershaps I should have clarified that – nullHip_nullSquare Jul 15 '20 at 19:10
-
@nullHip_nullSquare This solution worked beautifully :) (and no obj-c selector needed, to boot!) – Eduardo Perez Feb 06 '22 at 19:28
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)
}
}

- 174
- 1
- 7
Swift 4
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.updateTime), userInfo: nil, repeats: true)
Update function
@objc func updateTime(){
debugPrint("jalan")
}

- 2,129
- 21
- 20
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")
}

- 893
- 9
- 10
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
}
}

- 104,963
- 20
- 228
- 340

- 21
- 1
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")
}
}
}

- 18,379
- 16
- 47
- 61

- 39
- 2
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
}
}

- 614
- 1
- 8
- 15
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()
}
}
}

- 387
- 3
- 3
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
}
}
}

- 2,371
- 5
- 14
- 26

- 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
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)
})

- 231
- 3
- 6
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"

- 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)
}

- 2,225
- 2
- 24
- 36