The app will contain two labels (one for our title and one for our numeric timer display), and two buttons (a start/stop button, and a reset button). We will first lay out our main view, which will contain these elements and then use the interface builder (i.e. storyboard) to hook our view into the controller (IBOutlets and IBActions). Finally, we will turn to the business logic of the app. In the end, we will have two imports and a view controller. We will add three methods to the view controller class: two actions methods and one helper method. The tutorial will be broken down into a series of just over thirty steps with screen caps to provide a quick visual aid.
The first thing you'll need to do, if you haven't already, is download the latest version of Xcode 6 (currently in beta as of this writing). If you are completely new to Xcode, you may find it difficult to navigate the interface. There are tons of great guides to Xcode that can be found online such as this one.
On initial startup, Xcode will present you with its ‘Welcome’ screen and offer several options. Select the “Create a New Xcode Project” panel from the window (see figure 1). If it does not present you with this screen, select File->New->Project from the menu bar.
Figure 1 |
Figure 2 |
Figure 3 |
Your project should have opened to a screen similar to the one in figure 4.
Figure 4 |
Figure 5 |
Now that we have our project’s initial setup completed, let’s get down to business! From the Project Navigator, select the Main.storyboard file. See figure 6.
Figure 6 |
Figure 7 |
From the Object Library toward the bottom of the right panel in Xcode, select Label (figure 10) and then drag and drop it at the left style guide, but vertically centered into your single storyboard scene/view. See figure 11. The style guides are temporary visual placement lines that appear as you position view elements into your scene. This label will eventually provide the numeric display of our running stopwatch.
Figure 10 |
Figure 11 |
In order to make the label wider, we will select and drag the trailing edge of our Label element to the right of your scene until it meets the right most style guide. See figure 12.
Figure 12 |
Figure 13 |
Figure 14 |
From the Size Inspector in Xcode’s right panel change, the width of each button to 60 points. See figure 14.1.
Figure 14.1 |
Select the Assistant Editor from the top right corner of Xcode to display the storyboard and associated Swift file side-by-side. Now hide both the Navigator and Utilities panels by clicking on the appropriate panel buttons in the top right corner of Xcode. See figure 15.
Figure 15 |
Place your mouse pointer over the Label; then press and hold the control button while clicking and holding down your mouse button as you pan your mouse pointer across the screen and into the right side of Xcode where your Swift file is located. You should see a blue line follow your mouse pointer. See figure 16.
Figure 16 |
Once in the class area, as shown, release the mouse button and the control as well. You will see a dialogue box prompting you for the name of your outlet. Name it ‘numericDisplay’. See figure 17. This will add the necessary outlet code to your Swift controller class. An outlet is a reference pointer to an element inside your storyboard. Creating outlets allows for easy access to objects in your storyboard. After naming the outlet, your screen should look like figure 18.
Figure 17 |
Figure 18 |
Go ahead and connect the buttons as outlets as well. Name the left button ‘resetButton’ and the right button ‘startStopButton’.
In a similar fashion to the newly created outlets, we will now create action methods for each button. This time we will drag the blue line toward the bottom of the file but inside the class body. Name the left button resetButtonPressed and startStopButtonPressed for the right button. In the dialog box, make sure you select 'Action' from the Connection drop down menu. See figure 19. Action methods are the functions in your class that get messaged/called when the button that is associated with the connected method is triggered by an event. An event is initiated when the user interacts with any of your buttons.
Figure 19 |
Figure 20 |
To accurately update our numeric display label, we need to tie it in to a mechanism that will update at very precise time intervals. To access this functionality, we need to import the appropriate class library. From Xcode’s menu bar, select the Window tab drop down menu then select Documentation and API Reference. See figure 21. The documentation search window should appear. In the search bar type CADisplayLink and locate the appropriate documentation. Read through the documentation and familiarize your self with the CADisplayLink class. This class is very useful when your code needs precise timing control.
Figure 21 |
Importing a framework into your file gives you access to its classes and functionality. In your ViewController class, add the following var properties just below the @IBOutlet properties. Add the code in lines 5 and 6 below:
‘var’ is short for variable and displayLink is our object pointer of type CADisplayLink. We use this pointer to hold a strong reference to an instantiated CADisplayLink object that we will create in a few moments. We want a strong reference to an instance of CADisplayLink to be able to access it throughout the various parts of our class. We also define a lastDisplayLinkTimeStamp of type CFTimeInterval. This variable will store a running tally of the total elapsed time.
Now let’s set the default view element values for our numeric display label and our two buttons. Add the code below to our viewDidLoad() method:
In your viewDidLoad() method now add the lines of code from the snippet below:
The first new line of code above (line 16) creates an instance of a CADisplayLink object, and assigns this class, i.e. “self,” as the target for messages that inform us of a display refresh rate update. This occurs in the first parameter of the CADisplayLink(<first parameter>, <second parameter>) method call. In the second parameter we pass the name of the method that we would like to be called when there is a display refresh rate update. We will define this method shortly. The second new line of code (line 9) ensures that the display link does not begin its updates until we press the Start button in our user interface. The third new line of code (line 12) schedules the display link to begin sending notifications to our instance method about display refresh rate updates. The fourth new line of code (line 15) simply initializes our elapsed time running tally variable.
The next step is to define the method that will be called when the display link has an update. Add this code to the bottom of the viewController.swift class, i.e. inside the final class curly brace:
Now for the logic—we are almost there! In the newly created function add the following lines of code:
The first new line of code (line 3) updates our running tally. The second (line 6) formats our running tally into a string that only shows the last two significant digits. The third (line 9) updates our numeric display label.
Let’s move over to the startStopButtonPressed(…) method. This method is called anytime the user presses the button situated to the right in our stop watch scene. When this button is pressed we want to toggle the display link’s “paused” Boolean value. Add the following line of code to this method.
At this point you can run your project and press the start button to see your stop watch in action! Woohoo! Again, if you get any errors and the project does not build correctly, read the error message(s) carefully and see if you can troubleshoot the problem.
Let’s shift our focus to the Reset button. What do you think this button should logically do? It should pause the display link to prevent it from sending us any further updates, then set the numeric display label to zero, and update our Start/Stop button state. In the resetButtonPressed(…) method add the following lines of code:
Let’s now complete our code project by adding the last few lines of code for our Start/Stop button. In startStopButtonPressed(…) add the code in bold:
Our label text string will not be modified if our code does not fall through our first conditional if statement. If, however, the display link is paused, then we check the running display link tally. If this tally is greater than zero, then we display the resume button since pressing this button again will not reset our running tally. If it’s equal to zero then we display the start text. The button text is set in the last line of code.
Your final ViewController file should look like this.
Finally, let’s add a title label. Go back to the main storyboard. From the object list at the bottom of the File Inspector in the right pane of Xcode, drag a Label to the center/top of your main view. Size it as you like, and provide it with a text title such as “Stopwatch”. The final product should look something like the three screencaps below, showing the default, running and paused states:
Default State |
Running |
Paused |
And that concludes our simple Stopwatch app tutorial! We leave you off with a question for further reflection. Notice that our chronometer output is not formatted for standard time. Our implementation increments our minor units, values to the right of the decimal point, from .00 to .99 before increasing the the major unit by one. Although this is a correct unit of measurement, it is not in the ubiquitous standard time format. In the standard format the minor unit, a.k.a the second, is incremented from .00 to .59 before the next major unit, i.e. a minute, is increased by one. Since there are many ways to implement this, some being more efficient than others, we leave this consideration to the reader as an exercise. Post your own solution below. And, as always, feedback, suggestions, and angry tirades are welcome in the comments.
This project can be found on GitHub.
The Stopwatch app and tutorial was originally authored by Stefan Agapie, a Senior iOS Software Engineer, and then adapted for the present piece.
Helpful tutorial! But why you didn't used NSTimer?
ReplyDeleteHey Andrey. From my understanding of apple docs NSTimer is typically used for scheduling that is none animation related. I consider updates to the stopwatch display to be animation related scheduling so therefore we used the CADisplayLink.
DeleteHow would you do this without a resume button?
ReplyDeleteDo you mean just toggle between Start and Stop?
DeleteReplace:
if self.lastDisplayLinkTimeStamp > 0 {
buttonText = "Resume"
} else {
buttonText = "Start"
}
with,
buttonText = "Start"
(sorry for the delayed response)
Hello!
ReplyDeleteI was solving the same problem today and your guide pointed me to the right trail with CADisplayLink and other hints. Thank you so much.
Also i've found this question on stackoverflow:
http://stackoverflow.com/a/33586577/1855792
From your guide and answers on stackoverflow I've assembled a clean and reusable Stopwatch class. Thought i should share it with you and your audience because of your contribution, so here it is:
https://gist.github.com/Flar49/06b8c9894458a3ff1b14
Feel free to use it. Thanks again :)