Make App Pie

Training for Developers and Artists

Swift Swift: Using NSTimer to Make a Timer or Alarm

frame 0730 042315With 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 at NSTimer, 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 the language. 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 to Width: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 select Equal 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, white lettering, 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 up and select for Update Frames Items of new Constraints.

Drag two more labels to the storyboard position one to the left of the switch labeled Up and 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.

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

From the switch make an outlet countingDown

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

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

@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:

    @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 method timerDidEnd, 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

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.

//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:

@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:

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 time timer 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 your timerDidEnd method to this:

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:

  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 an integer 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:

    //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.

@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:

     @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:


    @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:

var isTiming = false

In starTimer, add isTiming = true to the if clause code block and isTiming = false to 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

//
//  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

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

        }

    }

   }

18 responses to “Swift Swift: Using NSTimer to Make a Timer or Alarm”

  1. Are there limitations on using the NSTimer when the user might not continue interacting with the application and the device goes to sleep?

    1. My watch is still in the queue for delivery, so I have not tested it. However, most of the extension code runs and stays on the phone, not the watch. For developers, the watch is literally and figuratively an extension of the phone. Basically, You are not running apps on the watch, just the view of MVC. The model and controller are on the phone. NSTimer should happily work away even if the watch sleeps.

  2. I regret to inform you that NSTimer will be halted when the iPhone is locked. The period of time will be between 0 to 10 minutes.

    You could try with background threads (dispatch_get_global_queue, dispatch_async) and/or work around beginBackgroundTaskWithExpirationHandler.

  3. I’ve been building a timer app but am searching for a way to alert or alarm when the timer is done. In your example, if someone isn’t staring at the watch, how will they know it’s pizza time? Everything I’ve read says that you can’t activate the taptic engine. Is the only option to throw a notification and generate a glance? Seems like an obvious thing for a watch to do. In the default stopwatch app on Watch it will notify you, but those must be private APIs.

    I really appreciate this series of blog posts, and any help you can offer.
    Thanks.

    1. At this point,and to my knowledge, a notification is the only way to go. Another one of those things I’d like to see introduced at WWDC2015 in early June ( not to mention my watch getting delivered ). As soon as I get through the foundation classes, I’ll cover notifications.

  4. […] via Swift Swift: Using NSTimer to Make a Timer or Alarm | Making App Pie. […]

  5. […] is not  all we can do with NSTimer. In a separate post, I’ll cover how to use it in a repeat mode to make a full stopwatch / timer in iOS.  Our next […]

  6. […] Swift Swift: Using NSTimer to Make a Timer or Alarm […]

  7. Does anyone can tell me how to display the hours, minutes and seconds in this timer?

    1. depends if you want fractions of seconds or integer seconds. I’ll give you the integer version (which can be expanded to days, weeks, years, etc) and leave this as a hint for the fractional one. All you are doing is diving by the number of seconds in some time period. The whole part of that division is the number of that time period. The remainder you use in the next calculation down.

      func timeString(time:NSTimeInterval) -> String {
          let timeSeconds = Int(time)
          let hours = timeSeconds / 3600 //60minutes * 60seconds = 3600 seconds
          let hourRemainder = timeSeconds % 3600 //number of seconds less than an hour
          let minutes = hourRemainder / 60  
          let seconds =  hourRemainder % 60 //number of seconds less than a minute
          return String(format:"%02i:%02i:%02i",hours,minutes,seconds)
      }
      

      From this structure you can figure out a float or Double version of this.

  8. What would be the framework or library to use to set up an alarm clock in Swift? I want to be able to hit an image in my app and go to a page that has three selections on a alarm to set. Like 3 days 6 hours, 4 days, 5 days 8 hours. User clicks one and Alarm is set which will notify in said amout a of time. Ns timer goes off in background I realize so that is out so how would I go about that?

    1. Most of what you want is here. However, you might want to look at the stuff I did on notifications. this And This

  9. Saurabh anand Avatar
    Saurabh anand

    what if user is inactive

    1. Then this won’t work. You have two possibilities, either schedule a local notification or set the timer into the background. See here And here for more

  10. […] discuss timers in more detail here. Basically startTimer starts a timer with a 1 second interval that repeats. I’m being course […]

  11. Hi! I get an error with the selector and the instance method “timerDidEnd”:

    No method declared with Objective-C selector ‘timerDidEnd:’

    Do you know a fix?

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.