Completed applications

in iOS, Swift, watchOS

Countdown to Rouge one – iOS and watchOS tutorial


I’m a huge Star Wars fan, I loved the Force Awakens and since the last trailer released for the Rouge One the hype is starting to grow again. To honor the release of the new film, today’s tutorial will be a simple countdown fan application for iPhone and iWatch devices telling us how much time has left until the movie is released. This app concept is a perfect excuse to play with some fancy features of watchOS and create something “useful”.

Creating new project

Watch apps are not standalone apps, meaning that they require to pair with an app running on your iPhone. Hence, watchOS apps are just extensions of the existing iPhone apps. This allows your Apple Watch to have communication with the internet, save battery and most importantly from developers point of view, share the same code with the iOS application. Go ahead and create a new project from your Xcode and select watchOS application. This will create all the necessary files for both your iPhone app and the app for your watch.

New watchOS project

New watchOS project

Creating iPhone application

We will first create layout for our iPhone application. To keep things real, let’s download a proper font to match or Star Wars theme. You can find an exact match on http://www.fontspace.com/boba-fonts/star-jedi. Once you download it, extract the zip file and move ttf files to your application. After drag and drop make sure you check “Copy if needed” flag. After that, open your Info.plist file and create a new dictionary called Fonts provided by application and add the font names you just imported. If all goes well you should see your new fonts in the storyboard options. For this tutorial, I’ve also downloaded an image from the Rouge One trailer which we will put above our counter. This image will be added to two assets folder, one for iPhone app and the other one in Extension folder which will be used in watchOS app.

Open your Main.storyboard and for now create a view with black background and import an image view and four labels. Put the image view on top, three labels under the image and one label at the very bottom. Align all elements horizontally and add a bit of spacing between each. Final layout should look something like this:

iPhone layout

iPhone layout

Next we need to figure out how to calculate the remaining time between whenever you open your app and the release date of the movie. We will create a class for this inside WaatchKit Extension folder so we can reuse the same code for iPhone and watch app. Create a new class and make sure it has target membership set to both the app and the WatchKit extension. You can review this by clicking on the class file and in the File inspector navigate to target membership.

 

At the very top of the class create two variables

let dateFormatter = NSDateFormatter()
var timeEnd: NSDate?

 

We will use date formatter to create end date from string and timeEnd variable to track how much has time has left. By using a compare function from NSDate()  we can easily figure out the exact remaining time just by simply checking if the result of the comparison between the two dates is ascending or descending. We are going to compare timeEnd with the current time hence if there is any remaining time NSComparisonResult will be descending because left operand is larger than the right one. By using NSCalendar we can break the NSDate into components such as days, hours or minutes. The full code for this function, which returns string as a result is given below:

import Foundation

class Counter {
    
    let dateFormatter = NSDateFormatter()
    var timeEnd: NSDate?
    
    func getDifference() -> String {
        
        dateFormatter.dateFormat = "yyyy-MM-dd"
        
        let timeNow = NSDate()
        timeEnd = dateFormatter.dateFromString("2016-12-16")
        
        if timeEnd!.compare(timeNow) == NSComparisonResult.OrderedDescending {
            let calendar = NSCalendar.currentCalendar()
            let components = calendar.components([.Day, .Hour, .Minute, .Second], fromDate: timeNow, toDate: timeEnd!, options: [])
            
            return String(components.day) + ":" + String(components.hour) + ":" + String(components.minute) + ":" + String(components.second)
            
        } else {
            return "Hit the cinema!"
        }
    }
    
}

 

Now that we have our business logic, go back to your iPhone main view controller and drag IBOutlet connection for the counter label.
At the very top of the controller instantiate the Counter class and timer variable which should be NSTimer type. In addition implement UIViewController delegate methods, viewWillAppear and viewWillDisappear. We will use these to add
and remove timer for the counter update. Your controller should look something like this:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var countLabel: UILabel!
    var timer: NSTimer?
    let counter = Counter()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    override func viewWillAppear(animated: Bool) {
       
    }
 
    override func viewWillDisappear(animated: Bool) {
       
    }
}

 

Inside viewWillAppear method write the following:

timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)

 

This will schedule a timer which will call update function each second. Inside of that function we will implement the logic for updating our UI with the time calculated by getDifference() method from Counter class. At this moment if you try to compile the app, Xcode will show you an error because we haven’t implemented the update method yet:

func update() {
    countLabel.text = counter.getDifference()
}

 

Quite simple isn’t it? Since our method getDifference() always compares the current time (NSDate) with the end time, and our timer triggers this check every second we get a nice real time counter . One last thing that has to be done is to invalidate the timer inside viewWillDisappear. Entire code of this controller should look like this:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var countLabel: UILabel!
    var timer: NSTimer?
    let counter = Counter()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    override func viewWillAppear(animated: Bool) {
        timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
    }
    
    func update() {
        countLabel.text = counter.getDifference()
    }
    
        
    override func viewWillDisappear(animated: Bool) {
        timer?.invalidate()
    }
}

 

If we build and run the following code the output should look something like this:

Rouge One countdown iPhone app sample

Rouge One countdown iPhone app sample

Looks cool right? Now moving to the fun part, watchOS.

 

Creating watchOS application

Open up your Interface.storyboard and delete everything from it. You can also safely delete the default controller as well. Next, drop a new Interface Controller from the object library and mark it as initial controller.  You can consider Interface controller as the equivalent to View Controller on iOS. Layout inside interface controller is organized using groups. Group is a container for one or more interface objects. A group is conceptually similar to a superview in UIKit. Our watchOS application will have only one interface and inside that interface we are going to have two sections: image and counter. This means that we will import two groups from the object library. All the UI modifications and manipulation is done by modifying various properties in attributes inspector. If you are used to AutoLayout and constraints this concept might be a bit strange at first but after few minutes it starts to make sense. 

Inside the first group drop an image view and inside the second one a label. We will create similar UI as for the iPhone application but with less details.

Interface layout

Interface layout

Now create a new controller for this interface and name it CountdownInterfaceController. Make sure it inherits from WKInterfaceController. Again, concept is quite similar to UIViewController, you will notice two three methods awakeWithContext, willActivate, didDeactivate which act almost the as viewDidLoad, viewWillAppear and viewWillDisappear. Due to the fact that our Counter class is shared we can now simply do the same thing as we did for the iPhone application without writing anything new. Drag the label outlet from the interface to your controller and again instantiate two variables counter and timer.

 

import WatchKit
import Foundation


class CountdownInterfaceController: WKInterfaceController {

    @IBOutlet var countLabel: WKInterfaceLabel!
    let counter = Counter()
    var timer: NSTimer?
    
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        
        // Configure interface objects here.
    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
        
        timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
    }
    
    func update() {
        countLabel.setText(counter.getDifference())
    }
    
    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        timer?.invalidate()
        super.didDeactivate()
    }

}

 

Notice only one difference in the update method, we have to use setText method from WKInterfaceLabel instead of accessing the property like with UILabel.
If this is your first watchOS application you most likely do not have a simulator in your list since they do not come by default. It is easy, however, to add one. Simply open the simulator list and click Add Additional Simulator. In the bottom left corner click on the plus icon to add a new simulator. You can pick either 42 or 38 mm device.

Adding a new simulator

Adding a new simulator

Next you will be asked to add iPhone simulator which is linked to your watch. Make sure that you quit any running simulators before you run watchKit app since it can happen that your app does not start which is probably a Xcode bug Apple will fix soon. If everything is working, when you build, two different simulators should appear in your dock, one for iPhone and one for watch. Application on your watch should open automatically and you should see a countdown similar to what we implemented for the device. We can also run the apps in parallel and the time on the counter should be the same. There’s an example of how it should look like:

Completed applications

Completed applications

And that would be it. You have sucessfuly created a simple watchOS application with only few lines of code which acts as an extension to the existing iOS app. It was a straightfoward process and the only thing you should realy think through is how to structure your business logic to avoid writing redundant and spaggeti code. If you have any questions about the tutorial or suggestions feel free to write in the comments or send me a message on Facebook, Twitter or LinkedIn.