Build a count down timer with Swift 3.0
Here you will see how to build a simple timer for iOS using Swift 3.0.
I will assume you have a basic understanding of the Swift programming language, Xcode and the Storyboard. If you need help with the basics or setting up the UI in storyboard check out www.raywenderlich.com for some awesome beginner tutorials.
Timer Class
We will use the Timer class provided by Apple. A timer is defined in the documentation, like this:
“A timer waits until a certain time interval has elapsed and then fires, sending a specified message to a target object.”
“For example, you could create an Timer
object that sends a message to a window, telling it to update itself after a certain time interval.”
The Timer class was created to make it easy to trigger actions, (like updating labels or firing methods), at specific moments in time.
Building the Timer
- Create a new “Single View Application,” give it a name, and set the language to Swift.
- Make a new view controller in Storyboard, or use the pre-existing default view controller. Add a label. (This label will show the starting time and the counting down in seconds, minutes and hours.) Add three buttons: “Start”, “Pause”, & “Reset”. My initial set-up looks like this:
Note: What you set as placeholder text in the label won’t matter since we’ll provide the labels text within the code. I made it look like a timer, but it would work just as well kept as the word “label.”
The font you set for the label, however, will be shown when the program runs. I chose Courier New because it is monospaced(all letters/number are the same width)and won’t shift around as the numbers count down.
3. Connect the label and buttons from the storyboard to the view controller class.
Shortcut: To open the view controller file in the assistant editor from Storyboard hold Option and click on the class file in the Navigation inspector/menu on the left.
I’ll assume you know how to connect the labels and buttons to the proper class.
4. Next leave the Storyboard and go to the View Controller class. Underneath the timerLabel outlet create the following variables:
var seconds = 60 //This variable will hold a starting value of seconds. It could be any amount above 0.var timer = Timer() var isTimerRunning = false //This will be used to make sure only one timer is created at a time.
Your code so far should look like this:
You can erase the didReceiveMemoryWarning() function we will not need it in this demo.
5. Make a new method containing the following code:
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
}
This method call initializes the timer. It specifies the timeInterval (how often the a method will be called) and the selector (the method being called).
The interval is measured seconds so for it to perform like a standard clock we should set this argument to 1.
timeInterval: 1
The syntax is a bit confusing in the selector argument. The following should work well for specifying which method to call at the interval.
selector: (#selector(ViewController.updateTimer))
updateTimer is the name of the method that will be called at each second. This method will update the label. We will define this method below in the next step.
userInfo: nil, repeats: true
Don’t worry about userInfo for now, set it to nil. And repeats should be set to true, because we want the timer to automatically call itself again to keep running.
6. Next, call the runTimer() method within the body of startButtonTapped.
Update Timer Method
7. Create the following method and code in its body:
func updateTimer() {
seconds -= 1 //This will decrement(count down)the seconds.
timerLabel.text = “\(seconds)” //This will update the label.
}
In reality, the ticking of the seconds in the timer is merely a UILabel being updated every second. Nothing more complicated than that!
Your code should look like this so far:
Note: I put the timer code into a secondary method (called runTimer) which is being called in the startButtonTapped method. I did this because we’ll need to reuse this method later.
If you run your timer it should begin counting down from 60 seconds. But it will you will not be able to stop or pause it. And it will count past zero into negative numbers. So let’s continue!
Pause/Resume Implementation
8. To add the Pause and Resume functionality we will need to add implementation to the pauseButtonTapped method.
Swift’s Timer class has a method called invalidate(). It will stop the timer, but not reset the current value of seconds. This will be useful for resuming.
First add a boolean class variable that will keep track of if the pause button should be allowed to Resume. In other words, has pause been tapped before or not. Since pause has not been tapped when the app start up, set this to false.
var resumeTapped = false
Then, use an if-else statement to alternate between pause and resume.
@IBAction func pauseButtonTapped(_ sender: UIButton) {
if self.resumeTapped == false {
timer.invalidate()
self.resumeTapped = true
} else {
runTimer()
self.resumeTapped = false
}
}
The first time the pause button is tapped the resumeTapped will be false and the timer will be stopped with timer.invalidate().
The following time the pause button is tapped runTimer() will again initialize the timer. Note: the seconds have not been overwritten so it should begin where it left off.
And this will continue alternating as resume is tapped.
Reset Implementation
9. It’s also important to be able to reset the timer back to the starting point. In the resetButtonTapped method we will stop the timer and add functionality to reset the number of seconds to zero.
@IBAction func resetButtonTapped(_ sender: UIButton) {
timer.invalidate() seconds = 60 //Here we manually enter the restarting point for the seconds, but it would be wiser to make this a variable or constant. timerLabel.text = “\(seconds)”
}
10. Go ahead and run your timer!
Your timer should run, pause/resume, and reset.
But you’ll notice that it does not look like a clock and keeps counting the seconds far beyond 0 into negative numbers.
With the current set up you only see time represented in seconds, which could get confusing if you count down from anything longer than a minute.
Formatting Hours, Minutes, Seconds
11. To format the label to show minutes and hours we will need to convert the seconds with the TimeInterval class.
Make a new method called timeString. It will take a time interval or Integer and return a String with the formatted time. The label will now update using this string.
func timeString(time:TimeInterval) -> String {let hours = Int(time) / 3600
let minutes = Int(time) / 60 % 60
let seconds = Int(time) % 60return String(format:”%02i:%02i:%02i”, hours, minutes, seconds)}
Inside of the updateTimer method replace
timerLabel.text = “\(seconds)”
with
timerLabel.text = timeString(time: TimeInterval(seconds))
This will send it through the timeString method where it will be formatted first and then set as the text label.
Your timer should have better formatting, like this:
And your code should now look like this:
It seems to be running well but try tapping on the Start button multiple times and you’ll see something is definitely wrong.
Fixing the Multiple Timers/Speeding Up Bug
12. You might have noticed if you hit the Start button multiple times in a row the time will start counting faster and faster erratically.
This happens because a new Timer object is being created at each tap of the button. Then each of these new timers is taking control of the label simultaneously causing the label to change at an increasingly faster speed.
This is where the isTimerRunning variable we created at the beginning will come into play. It will keep track of if the timer is running or not.
var isTimerRunning = false //Look to make sure this variable has been declared in the View Controller class.
Next, inside the body of startButtonTapped, add an if statement surrounding the creation of the new timer: if isTimerRunning == false {}.
@IBAction func startButtonTapped(_ sender: UIButton) {
if isTimerRunning == false {
runTimer()
}
}
Inside runTimer(), set isTimerRunning to true.
Inside the resetButtonTapped we should set isTimerRunning to false again because we are stopping and do not want to restart the same timer.
@IBAction func resetButtonTapped(_ sender: UIButton) {
timer.invalidate()
seconds = 0
timerLabel.text = timeString(time: TimeInterval(seconds)) isTimerRunning = false
}
Stop Timer And Send Alert When Seconds Are Less Than One
13. One of the most important features of the timer is that it stops when the time has run out.
Add an if statement within the updateTimer method that checks if the number of seconds are less than one. If so, stop the timer with invalidate() and add implementation call an alert or do some other action to let the user know the timer has reached the end.
For this example, I’ll just stop the timer.
func updateTimer() {
if seconds < 1 {
timer.invalidate()
//Send alert to indicate "time's up!"
} else {
seconds -= 1
timerLabel.text = timeString(time: TimeInterval(seconds))
}
}
Now run your timer, when it gets to zero seconds left it will stop.
This is how your methods should look at this point.
Finishing Details
There are several finishing touches that will make the timer more user friendly.
Switching Label with Pause/Resume.
14. First, the Pause button is actually duel-purpose. It is “Pause” when the timer is running and “Resume” when the timer has been paused.
The button’s title should switch between pause and resume to reflect it’s current purpose.
To add this implementation, first make an outlet for the pause button by connecting the button from storyboard to the class. Give it a name like “pauseButton.”
Next, in pauseButtonTapped set the title to “Resume” once the pause button has been tapped and reset the title to “Pause” once the button is tapped again.
In Swift 3 the syntax for setting the title on a button is the following:
self.pauseButton.setTitle(“Resume”,for: .normal)
The pauseButtonTapped method should look like this:
@IBAction func pauseButtonTapped(_ sender: UIButton) {
if self.resumeTapped == false {
timer.invalidate()
self.resumeTapped = true
self.pauseButton.setTitle(“Resume”,for: .normal)
} else {
runTimer()
self.resumeTapped = false
self.pauseButton.setTitle(“Pause”,for: .normal)
}
}
Enabling/Disabling Start and Pause Buttons
15. Something else that will make the app more user-friendly and help avoid bugs is to enable/disable the pause and start buttons according to their purpose.
Before you start the timer the resume button should not be enabled.
Once the timer is running the start button should no longer be enabled.
To disable the “Start” button after the timer has begun, make an outlet for it called startButton.
In the startButtonTapped method, disable the start button on the first tap by adding it to the if statement. This will ensure that once the timer has been started the first time the start button will no longer be enabled.
@IBAction func startButtonTapped(_ sender: UIButton) {
if isTimerRunning == false {
runTimer()
self.startButton.isEnabled = false
}
}
Likewise, we do not want the Pause button to be enabled before Start has been tapped. So in viewDidLoad, disable it when the app loads.
override func viewDidLoad() {
super.viewDidLoad()
pauseButton.isEnabled = false
}
Once the timer has started we need to make sure that Pause is enabled. Enabling it in the runTimer method ensures that if the timer is running the pauseButton can be tapped.
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
isTimerRunning = true
pauseButton.isEnabled = true
}
Lastly, if the reset button has been tapped then Pause should be disabled, so add it to resetButtonTapped.
@IBAction func resetButtonTapped(_ sender: UIButton) {
timer.invalidate()
seconds = 60
timerLabel.text = timeString(time: TimeInterval(seconds))
isTimerRunning = false
pauseButton.isEnabled = false
}
Congratulations, you’ve successfully built a timer with Swift 3!
15. Now run your timer!
Completed Project Files in Github
For the completed project source files go to Github.
Further reading & sources:
https://developer.apple.com/reference/foundation/timer
https://www.youtube.com/watch?v=bigFGOvywa4
http://www.ios-blog.co.uk/tutorials/swift/swift-nstimer-tutorial-lets-create-a-counter-application/
https://www.hackingwithswift.com/example-code/system/how-to-make-an-action-repeat-using-timer