In this tutorial we are going to learn to control time
We are going to do that by making a binary counter using NSTimer in Swift.
The final result will look like this:
You can get the full code here.
First let’s look a bit a the pieces that we are going to use.
NSTimer
Basically a timer executes some code after a time interval one or more times. With NSTimer
you create timer objects. A timer object waits for a specific time interval to elapse and then it fires. When it fires, the timer sends a message to the target object associated with it.
In order to truly understand timers, you have to know a bit about NSRunLoop.
NSRunLoop
Each iOS application creates at least one NSThread object (the Main Thread) which represents the thread of execution of the application code. Each NSThread
object has an associated NSRunLoop that manages input sources such as: mouse, keyboard, touches, ports, connections and timers.
NSRunLoop
continuously executes a loop in which it checks for input events. One of the events that is checked for is the ‘timer interval elapsed event’. When the run loop detects this kind of event it will fire the method on the target associated with the timer.
Timers don’t work in real-time. The reason: there is a point in the loop (NSRunLoopMode
) in which the timer events are managed. The run loop can spend more time than the timer interval in other modes so the timer might get delayed.
The Binary Counter
Let’s do this!
Step 1: Create a new project
Open Xcode 6, create a new “Single Page Application” and select Swift as the programming language.
Step 2: Create the UI
Open Main.storyboard
.
- Drag one
UILabel
and twoUIButton
objects from the Object Library in the Utilities panel. Name the first button “Start” and the second button “Reset“ - In the Identity inspector, set the class of the single view controller on screen to
ViewController
. - In the Attributes inspector, set the Simulated Metrics -> Size to IPhone 4 inch.
Don’t forget to build the project after this step, to ensure that everything works fine.
Step 3: Add outlets and actions
Open Main.storyboard
and also open ViewController.swift
in the Assistant Editor (with the Main.storyboard
opened, press and hold the Option Option (⌥) button and then click on ViewController.swift
). Then to create the Outlet for the label and Actions for buttons: Right Click + Drag from the label/buttons to the ViewController.swift
, inside the class definition.
class ViewController: UIViewController {
@IBOutlet var labelForBinaryCount: UILabel!
...
@IBAction func start() {
}
@IBAction func reset() {
}
}
Step 4: Add the NSTimer object
Under the IBOutlet
declaration add two new variables: timer and binaryCount:
class ViewController {
...
@IBOutlet var labelForBinaryCount: UILabel!
var timer = NSTimer()
var binaryCount = 0b0000
...
}
In Swift, numbers can be represented in various formats by prefixing the digits: binary (0b
), octal (0o
) and hexadecimal (0x
). For example, the number 0b0011
is the binary representation of the number 3
.
Now lets create a new NSTimer
that will:
- fire after 1 second
- execute the function “
countUp()
“ - repeat at 1 second time intervals
Modify the start()
function:
class ViewController {
...
@IBAction func start() {
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "countUp", userInfo: nil, repeats: true)
}
...
}
Using scheduledWithTimeInterval(_:target:selector:userInfo:repeats:)
class method to create the timer will also add the timer automatically to the NSRunLoop
associated with the NSThread
in which the timer is created.
The alternative is to create the timer using an initializer and to manually add it to the current NSRunLoop
:
class ViewController {
...
@IBAction func start() {
//timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "countUp", userInfo: nil, repeats: true)
timer = NSTimer(timeInterval: 1.0, target: self, selector: "countUp", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
}
...
}
See NSRunLoopModes for other modes in which you can attach the timer.
Now lets create the countUp()
function and also a helper function that will update the label:
class ViewController {
...
func countUp() {
binaryCount += 0b0001
// if the counter reached 16, reset it to 0
if (binaryCount == 0b10000) { binaryCount = 0b0000 }
updateText()
}
// Update the text from the label, by always maintaining 4 digits.
func updateText() {
// Convert from Binary to String
var text = String(binaryCount, radix:2)
// Pad "0" to the left, to always have 4 binary digits
for i in 0..<4 - count(text) {
text = "0" + text;
}
labelForBinaryCount.text = text
}
...
}
Notice the call String(binaryCount, radix:2)
. This call creates a string using the Int
stored in binaryCount
, but by representing it in base 2 (binary representation)
And finally, in order to stop and reset the counter, let’s implement the method reset()
:
class ViewController {
...
@IBAction func reset() {
timer.invalidate()
binaryCount = 0b0000
updateText()
}
...
}
In order to stop the timer, the method invalidate()
has to be called on it. After this method executes, the timer stops and becomes invalid (there is no way to start it again). In order to start the timer again, a new NSTimer
instance needs to be created, as you can see in the start()
function.
You can get the code here.
Challenges
- Make the binary counter countdown from
1111
to0000
- Create a timer that individually controls each digit of the 4-bit binary counter
- use a timer to animate a view
- use a timer to end game rounds
- Add a timer that executes once after the main timer is reset and displays the final count in decimal.
- Create a timer object that accepts closures instead of the target-action mechanism (Hint: create a wrapper class around an NSTimer instance)
- Create a timer that accepts closures, without creating a wrapper around NSTimer (Hint: seeCFRunLoopTimerCreateWithHandler)
If you found this useful, please take a moment and share it with your friends
How would I change this to count up in a normal way. Not hexidecimal, binary or Octogonal?
You can change the updateText() function to:
func updateText() {
labelForBinaryCount.text = “\(binaryCount)”
}
The binaryCount variable is the actual normal count. Build and run and you will see the result.
Binary, Hexa, Octal are only representations of the same number.
For example if you want to assign the number 3 to this variable you can do it like this:
binaryCount = 3 (decimal)
binaryCount = 0b0011 (binary)
binaryCount = 0o3 (octal)
binaryCount = 0x3 (hexa)
So as one last step you can rename the “binaryCount” variable to “timerCount” and change all 0b…. values to their decimal equivalents and it will work the same.
Using Hexa instead of Decimal for the same variable is sometimes needed. For example, many designers will give you the colors for a design in their hexadecimal representation. (0xff0000 == Red color).
Thanks that works. You should open up a help forum on the site!
for i in 0..<4 – count(text) {
using swift im getting an error:
cannot invoke count with an argument list of type (string)
in swift 2.0 you get the number of characters like this: text.characters.count
Really enjoyed this article and got me working quickly with Swift. Well done!