Swift using NSTimer to make a timer or alerm

With the launch of the Apple Watch it’s time for timers in iOS and WatchKit. Learning to use timers is a very important skill for most developers. There are many places we need to schedule regular intervals of time to do things. In my WatchKit series I’m writing a workout interval timer which tells the user when to switch intervals. Timers can tell the system to do more than just alarms. If you are not using SpriteKit, Games and animations need the use of  timers to control the frames per second of animation. In this lesson, we’ll look atNSTimer, and what we can do with the timer. There are two ways most people use it: for a single event or for repeated events. We’ll discuss both.

Set up the Storyboard

Create a new Project named SwiftPizzaTimer in a single view template with Swift as thelanguage. Drag three labels, three buttons and a switch to the storyboard. Arrange the storyboard like this.

Screenshot 2015-04-23 05.30.11

You can use Auto layout in the Width:any Height:any size class, or set you size class toWidth:compact, height:any and just drag and drop your controls. To use auto layout, do the following:
Drag one label to the storyboard. Title it Pizza Timer. Give it a light gray background with black letters, center alignment and a font size of 46 point. Pin the label 0 points up 0 left and 0 right. Set the height to 64 points, and select to Update the frame of new Constraints.
Drag two buttons under the label and next to each other. On the button to the left, title it Start, make the font black with a size of 46 points and a green background. On the right title the label Stop with a size of 46 points, white lettering and a red background. Pin the Start label 15 points up, and 0 left. Do not update the frames. Pin the Stop label 0 left and 0 right. Select the Pizza Timer label and the Start button. In the pin menu selectEqual Heights. Select the Start button and the Stop button. In the pin menu, select Equal widths and Equal Heights. In the align menu with both Start and Stop still selected, select Top edges, and make sure the value is 0. Also select the Update Frames Items of New Constraints.

Drag another button to the storyboard. Label it Reset, with a blue background, whitelettering, and a font size of 46 points. Pin the Reset button 0 left,0,right, 20 down, but do not update constraints yet. Select both the Pizza Timer label and the Reset button and in the pin menu select Equal Heights and set Update Frames to Items of New Constraints.

Drag a switch to the storyboard. Set its state to off in the attributes inspector. In the align menu, Align the switch to Horizontal Center in Container. Pin the Switch 15 upand select for Update Frames Items of new Constraints.

Drag two more labels to the storyboard position one to the left of the switch labeled Upand one to the right Down. Give both a font color of White and a font of 36 points. Right justify the Up label. pin the Up Label 0 left and 15 right. Select the label and the switch, and in the align menu Align to centers, and Update Frames of new constraints. Select the down label. Pint the down label 15 left and 0 right. Select both the Down label and the switch. In the align menu Align to centers, and Update Frames of new constraints. Your finished layout should be this:

Screenshot 2015-04-23 06.17.07

Open the assistant editor and control-drag the outlets and actions. Start with the Pizza Timer label. Control drag and make an outlet timerLabel.

1
@IBOutlet weak var timerLabel: UILabel!   //label on top of the view

From the switch make an outlet countingDown

1
@IBOutlet weak var countingDown: UISwitch! //for use in iteration 2

From the three buttons control drag to make three actions: startTimer, stopTimerand resetTimer respectively:

1
2
3
4
5
6
@IBAction func startTimer(sender: UIButton) {
}
@IBAction func stopTimer(sender: UIButton) {
}
@IBAction func resetTimer(sender: UIButton) {
}

Make an Alarm

The most useful class method for NSTimer is scheduledTimerWithTimeInterval. It has a form like this:

class func scheduledTimerWithTimeInterval(
     ti: NSTimeInterval, 
     target aTarget: AnyObject, 
     selector aSelector: Selector, 
     userInfo: AnyObject?,  
     repeats yesOrNo: Bool) -> NSTimer 

The first parameter gives a time in seconds that the timer will run for. At the end of that time, it will call a method set by the second two parameters. If there is information that needs passing to that method, we do so in the userInfo parameter, which stores the information in the timer.  Often this will be a dictionary of several values, though it can be any object. The last parameter tells us if we will repeat the timer or just end.

Let’s add a timer to our code, change startTimer to this:

1
2
3
4
5
6
7
8
@IBAction func startTimer(sender: AnyObject) {
    timerLabel.text = "Timer Started"
    timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
        target: self,
        selector: "timerDidEnd:",
        userInfo: "Pizza Done!!",
        repeats: false)
}

Our timer schedules a time of timeInterval, and when done runs a methodtimerDidEnd, then stops. This is the classic alarm clock method of doing something. You set an alarm and it goes off at some time with a given message, then dies. Xcode complains we have not defined  interval or timer. Just below your outlet declarations add this

1
2
var timer = NSTimer() //make a timer variable, but don't do anything yet
let timeInterval:NSTimeInterval = 10.0

Our next step is declaring a method timerDidEnd. Add this to the code.

1
2
3
4
5
//MARK: - Instance Methods
    func timerDidEnd(timer:NSTimer){
        //first iteration of timer
          timerLabel.text = timer.userInfo as? String
    }

When the timer fires, It calls this method. We tell the user that the timer completed  with the Pizza Ready!! message. Because we set repeat to false, the timer will dismiss itself after this call. If we need any information about the timer, including whatever we stored in userInfo, we have a parameter in our method to access it, which we have our completion message. Since userInfo is of type AnyObject, we will  need to downcast it correctly before use.

Add the following to the stopTimer method:

1
2
3
4
@IBAction func stopTimer(sender: AnyObject) {
        timerLabel.text = "Timer Stopped"
        timer.invalidate()
    }

In our stopTimer method, we use the NSTimer method invalidate to stop and destroy the timer.  Once stopped, the app gives the user feedback to tell them the timer stopped.

Build and run. When the app loads, tap the start button. nothing happens for ten seconds, when you get this:

Screenshot 2015-04-23 06.53.52

Using Repeating Timers

Timers only show that a time event happened. In the example above that was pretty boring. Let’s change the code for more user feedback and have the timer count up or down the ten seconds of our timer. To do this, we’ll use the most common way of using a timer with repeat set to true. The strategy here is having lots of short timing events. At each time event we do some updating and check if we are at our target time, when we will shut down our timer and do our exit activities like we did with our first example.

Change the variables to this:

1
2
3
4
var timer = NSTimer() //make a timer variable, but don't do anything yet
let timeInterval:NSTimeInterval = 0.05 //smaller interval
let timerEnd:NSTimeInterval = 10.0 //seconds to end the timer
var timeCount:NSTimeInterval = 0.0 // counter for the timer

The timer is the same, but we changed our interval to 0.05 seconds. every 0.05 seconds we will update the timerLabel with a new time. There is a balance here, which you might want to experiment with. If you make too big an interval, there will be inaccuracy with your visible timer. If you make too small an interval, you use a large amount of resources. If you use too many, you might have an internal inaccuracy to your timer as well and the app will be unresponsive. My general rule is to set my interval for a half of the last digit of accuracy you are displaying. I am going to 0.1 seconds accuracy on my label, so I set the interval to 0.05.

Since interval is no longer telling me when ten seconds is up we need another constant to tell us that,  assigned to timerEnd. I also assigned a variable timeCount. Every timetimer fires, the app will either add or subtract the timeInterval from timeCount. When I reach zero on a countdown timer or 10 on a count up timer I stop. Change yourtimerDidEnd method to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func timerDidEnd(timer:NSTimer){
        if countingDown.on{
            //timer that counts down
            timeCount = timeCount - timeInterval
            if timeCount <= 0 {  //test for target time reached.
                timerLabel.text = "Pizza Ready!!"
                timer.invalidate()
            } else { //update the time on the clock if not reached
                timerLabel.text = timeString(timeCount)
            }
        } else {
            //timer that counts up
            timeCount = timeCount + timeInterval
            if timeCount >= timerEnd{  //test for target time reached.
                timerLabel.text = "Pizza Ready!!"
                timer.invalidate()
            } else { //update the time on the clock if not reached
                timerLabel.text = timeString(timeCount)
            }
        }

We use the switch countingDown to decide if we count up or down. If we count up, we add the time interval. If counting down, we subtract the time interval. We look for our ending condition. If true, we exit as we defined it in the last example. If not at the ending condition, we update the time display.

The time is in seconds, We need to format it to a form usable by most humans, which we do in the function timeString. Add this code:

1
2
3
4
5
6
  func timeString(time:NSTimeInterval) -> String {
    let minutes = Int(time) / 60
    let seconds = time - Double(minutes) * 60
    let secondsFraction = seconds - Double(Int(seconds))
    return String(format:"%02i:%02i.%01i",minutes,Int(seconds),Int(secondsFraction * 10.0))
}

We use the truncation power of integers to get minutes and seconds we can use in our return string of mm:ss.s. minutes takes a timeinterval of seconds and makes it aninteger then divides by 60 seconds. seconds subtracts out that many seconds. We get a digit for the tenth of a second by subtracting the number of seconds from the integer number of seconds.

We now have a display when the timer fires. We have not yet implemented a reset for the timer. Add this to your code:

1
2
3
4
5
6
7
8
//MARK: - Instance Methods
func resetTimeCount(){
    if countingDown.on{
        timeCount = timerEnd
    } else {
        timeCount = 0.0
    }
}

Depending on the switch, we have a different starting point. Counting down, we count down from timerEnd to 0. We do the reverse for counting up, starting at 0. Now that we have a reset function, we can define the action for the reset button.

1
2
3
4
5
@IBAction func resetTimer(sender: AnyObject) {
        timer.invalidate()
        resetTimeCount()
        timerLabel.text = timeString(timeCount)
    }

Whenever we reset, we will stop the timer first. For a reset we clear the count on the timer, the update timerLabel with the reset time. Our final change is to startTimer. Change the code to this:

1
2
3
4
5
6
7
8
9
10
@IBAction func startTimer(sender: AnyObject) {
    if !timer.valid{ //prevent more than one timer on the thread
        timerLabel.text = timeString(timeCount) //change to show clock instead of message
        timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
            target: self,
            selector: "timerDidEnd:",
            userInfo: nil,
            repeats: true) //repeating timer in the second iteration
    }
}

We changed the timer to be a repeating timer by changing repeats to false. We can build and run.

timerDemo

We have a working timer. You can change the countdown or count up by tapping the switch.

One more bug — Using Flags in Timers

It’s important for debugging to check all possibilities of use. If we reset the timer, then change our count direction, the timer fails. The timer believes it is already done. When we reset, we clear the counter. When we change the switch, we change the terminating value to be the same as the start value and the timer thinks it’s finished. We’ll need to change our start value when the switch changes and the timer is reset.

Go to the storyboard and open the assistant editor.  Control drag from the switch to the code. Make a new action countingDown. Add the following code to the action:

1
2
3
4
5
6
@IBAction func countingDown(sender: UISwitch) {
    if !isTiming {
        resetTimeCount()
        timerLabel.text = timeString(timeCount)
    }
}

We use a flag isTiming to decide if the timer is in the middle of a count. If it is not, which will happen after the timer is done or a reset and when we change the switch we reset the count. Otherwise we leave the count alone. Flags in timer are common. they are good ways of indicating the state of the timer while it is running repetitively, much in the same way valid works for a single timer. We still need a little setup for this to work. Add the variable to our properties at the top of the class:

1
var isTiming = false

In starTimer, add isTiming = true to the if clause code block and isTiming = falseto resetTimer, and just under the two timer.invalidate() in timerDidEnd
Build and run. Now the timer works right.

The Whole Code

Iteration 1 of timer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//
//  ViewController.swift
//  SwiftPizzaTimer
//
//  Created by Steven Lipton on 4/22/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//
//  Iteration 1 of the timer - a single time
//
import UIKit
class ViewController: UIViewController {
    //iteration 1 of the timer
    //MARK: - Outlets and properties
    @IBOutlet weak var timerLabel: UILabel!
    @IBOutlet weak var countingDown: UISwitch! //for use in iteration 2
    var timer = NSTimer() //make a timer variable, but do do anything yet
    let timeInterval:NSTimeInterval = 10.0
    //MARK: - Actions
    @IBAction func startTimer(sender: UIButton) {
        timerLabel.text = "Timer Started"
        timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
            target: self,
            selector: "timerDidEnd:",
            userInfo: "Pizza Done!!",
            repeats: false)
    }
    @IBAction func stopTimer(sender: UIButton) {
        timerLabel.text = "Timer Stopped"
        timer.invalidate()
    }
    @IBAction func resetTimer(sender: UIButton) {
    }
    //MARK: - Instance Methods
    func timerDidEnd(timer:NSTimer){
        //first iteration of timer
        timerLabel.text = "Pizza Ready!!"
    }
   }

Iteration 2 count up and down timer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//
//  ViewController.swift
//  SwiftPizzaTimer
//
//  Created by Steven Lipton on 4/22/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//
//  Iteration 1 of the timer - a single time
//
import UIKit
class ViewController: UIViewController {
    //iteration 2 of the timer
    //MARK: - Outlets and properties
    @IBOutlet weak var timerLabel: UILabel!
    @IBOutlet weak var countingDown: UISwitch!
    var timer = NSTimer() //make a timer variable, but do do anything yet
    let timeInterval:NSTimeInterval = 0.05
    let timerEnd:NSTimeInterval = 10.0
    var timeCount:NSTimeInterval = 0.0
    //MARK: - Actions
    @IBAction func startTimer(sender: UIButton) {
        if !timer.valid{ //prevent more than one timer on the thread
            timerLabel.text = timeString(timeCount)
            timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
                target: self,
                selector: "timerDidEnd:",
                userInfo: "Pizza Done!!",
                repeats: true) //repeating timer in the second iteration
        }
    }
    @IBAction func countingDown(sender: UISwitch) {
        if !timer.valid{ //if we stopped an
            resetTimeCount()
        }
    }
    @IBAction func stopTimer(sender: UIButton) {
        //timerLabel.text = "Timer Stopped"
        timer.invalidate()
    }
    @IBAction func resetTimer(sender: UIButton) {
        timer.invalidate()
        resetTimeCount()
        timerLabel.text = timeString(timeCount)
    }
    //MARK: - Instance Methods
    func resetTimeCount(){
        if countingDown.on{
            timeCount = timerEnd
        } else {
            timeCount = 0.0
        }
    }
    func timeString(time:NSTimeInterval) -> String {
    let minutes = Int(time) / 60
    //let seconds = Int(time) % 60
    let seconds = time - Double(minutes) * 60
    let secondsFraction = seconds - Double(Int(seconds))
    return String(format:"%02i:%02i.%01i",minutes,Int(seconds),Int(secondsFraction * 10.0))
}
    func timerDidEnd(timer:NSTimer){
        //timerLabel.text = timer.userInfo as? String
        if countingDown.on{
            //timer that counts down
            timeCount = timeCount - timeInterval
            if timeCount <= 0 {  //test for target time reached.
                timerLabel.text = "Pizza Ready!!"
                timer.invalidate()
            } else { //update the time on the clock if not reached
                timerLabel.text = timeString(timeCount)
            }
        } else {
            //timer that counts up
            timeCount = timeCount + timeInterval
            if timeCount >= timerEnd{  //test for target time reached.
                timerLabel.text = "Pizza Ready!!"
                timer.invalidate()
            } else { //update the time on the clock if not reached
                timerLabel.text = timeString(timeCount)
            }
        }
    }
   }
Advertisements

One comment on “Swift using NSTimer to make a timer or alerm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s