The new Apple Watch is going to be released soon, we are going to take a little time to build a quick app that displays the North American time zones. This app will allow the user to swipe between the timezones so they can easily know what time it is anywhere in Canada and the US.
This app is built on top of the Tutorial: Today Widget in Swift tutorial, since in that app we also made a timezone view. Since the Today Widget and the Apple Watch apps are very similar because they are both built using extensions to an existing app it should make it a little easier to build.
What is a Watch App?
If you don’t know much about the framework of what makes up a watch app, you should first read Get Started with Apple Watch and WatchKit. This will give you a great introduction to what a Watch App is, and how glances and notifications work with the watch.
Setup the Application Extension
- Select File > New > Project. Select the Single view application template, and call it WatchApp.
- Add the Application Extension Target. Select File > New > Target…. Then in the left pane, select Apple Watch and under that choose WatchKit App.
Set the language to Swift if is isn’t already and check both Include Notification Scene and Include Glance Scene are not checked. Click Finish and Xcode will set up the target and the files needed for the Watch interface.
- A message will popup asking if you want to activate the “WatchApp WatchKit App” scheme. Click Activate.
- Adding a WatchKit App target to your Xcode project creates two new executables and updates your project’s build dependencies. Building your iOS app builds all three executables (the iOS app, WatchKit extension, and WatchKit app) and packages them together.
The WatchKit app is packaged inside your WatchKit extension, which is in turn packaged inside your iOS app. When the user installs your iOS app on an iPhone, the system prompts the user to install your WatchKit app if there is a paired Apple Watch available.
Create the Watch App View
The terminology for Watch Apps are a little different than iOS apps, instead of calling them “views”, Apple uses the term “Interface”. WatchKit apps do not use the same layout model as iOS apps. When assembling the scenes of your WatchKit app interface, you do not create view hierarchies by placing elements arbitrarily in the available space. Instead, as you add elements to your scene, Xcode arranges the items for you, stacking them vertically on different lines. At runtime, Apple Watch takes those elements and lays them out for you based on the available space.
When creating your interfaces in Xcode, let objects resize themselves to fit the available space whenever possible. App interfaces should be able to run both display sizes of Apple Watch. Letting the system resize objects to fit the available space minimizes the amount of custom code you have to write for each device
- We are going to start by adding a label into the Watch App Interface, in Interfaces the label type is WKInterfaceLabel. This one is the name of the time zone for the time we are going to display. Drag the label out from the Objects inspector just like you would in an iOS app. Also be sure to align the label text to center so it looks nice, also set the Position – Horizontal to “Center” as well.
- Next we need to add another label for the time. We’ll use a standard label so we can easily change the date per time zone> Watch Kit comes with its own Date label called WKInterfaceDate (but that only displays the current date and time). This can be customized, for our purposes we only want to see the time, not the date, so change the Date format to “None” and customize the font to “System” with a size of 30, and make it “Bold” so the time stands out, also set the Position – Horizontal to “Center” and Vertical to “Center”.
Xcode supports customizing your interface for the different sizes of Apple Watch. The changes you make in the storyboard editor by default apply to all sizes of Apple Watch, but you can customize your storyboard scenes as needed for different devices. For example, you might make minor adjustments to the spacing and layout of items or specify different images for different device sizes.
- Now that we have our UI defined, let’s connect the IBOutlets with the code in the “InterfaceController.swift” file. You can do this many ways, just like an iOS app. Either Control-drag the UI object from the storyboard into the InterfaceController.swift or define the code and link up using the Connections Inspector.
Here is the code for the two objects we are linking:// // InterfaceController.swift // WatchApp WatchKit Extension // // Created by Brian Coleman on 2015-04-17. // Copyright (c) 2015 Brian Coleman. All rights reserved. // import WatchKit import Foundation class InterfaceController: WKInterfaceController { @IBOutlet var timeInterfaceDate : WKInterfaceDate? = WKInterfaceDate() 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() } override func didDeactivate() { // This method is called when watch view controller is no longer visible super.didDeactivate() } }
Watch App Navigation
For WatchKit apps with more than one screen of content, you must choose a technique for navigating between those screens. WatchKit apps support two navigation styles, which are mutually exclusive:
- Page based. This style is suited for apps with simple data models where the data on each page is not closely related to the data on any other page. A page-based interface contains two or more independent interface controllers, only one of which is displayed at any given time. At runtime, the user navigates between interface controllers by swiping left or right on the screen. A dot indicator control at the bottom of the screen indicates the user’s current position among the pages.
- Hierarchical. This style is suited for apps with more complex data models or apps whose data is more hierarchical. A hierarchical interface always starts with a single root interface controller. In that interface controller, you provide controls that, when tapped, push new interface controllers onto the screen.
Although you cannot mix page-based and hierarchical navigation styles in your app, you can supplement these base navigation styles with modal presentations. Modal presentations are a way to interrupt the current user workflow to request input or display information. You can present interface controllers modally from both page-based and hierarchical apps. The modal presentation itself can consist of a single screen or multiple screens arranged in a page-based layout.
For our project we are going to use a page based navigation so the user can swipe between the four time zones.
You configure a page-based interface in your app’s storyboard by creating a next-page segue from one interface controller to the next.
To create a next-page segue between interface controllers:
- In your storyboard, add interface controllers for each of the pages in your interface. Let’s add one for each of our time zones.
- Control-click your app’s main interface controller, and drag the segue line to another interface controller scene.
- The second interface controller should highlight, indicating that a segue is possible.
- Release the mouse button.
- Select “next page” from the relationship segue panel.
- Using the same technique, create segues from each interface controller to the next.
- The order in which you create your segues defines the order of the pages in your interface.
Add in Live Dynamic Data
We are almost done making our watch app, let’s add in the clocks to finish it up. The updateClocks() method is called every second to keep the time up to date.
// // InterfaceController.swift // WatchApp WatchKit Extension // // Created by Brian Coleman on 2015-04-17. // Copyright (c) 2015 Brian Coleman. All rights reserved. // import WatchKit import Foundation class InterfaceController: WKInterfaceController { @IBOutlet var easternLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var centralLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var mountainLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var pacificLabel : WKInterfaceLabel? = WKInterfaceLabel() override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Create a timer to refresh the time every second var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("updateClocks"), userInfo: nil, repeats: true) timer.fire() } override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() } override func didDeactivate() { // This method is called when watch view controller is no longer visible super.didDeactivate() } func updateClocks() { var time: NSDate = NSDate() let formatter:NSDateFormatter = NSDateFormatter(); var timeZone = NSTimeZone(name: "UTC") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" var formattedString = formatter.stringFromDate(time) var formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) timeZone = NSTimeZone(name: "US/Eastern") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.easternLabel?.setText(formatDateString) timeZone = NSTimeZone(name: "US/Pacific") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.pacificLabel?.setText(formatDateString) timeZone = NSTimeZone(name: "US/Mountain") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.mountainLabel?.setText(formatDateString) timeZone = NSTimeZone(name: "US/Central") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.centralLabel?.setText(formatDateString) } }
Test Your Watch App
- When you run the app you’ll see a blank view since our app doesn’t do anything except create the Watch App and the watch is shown in an External Display.
- Build and Run your app against the Watch App target.
- Then, switch over to your simulator and check that you have the External Display set for the watch. iOS Simulator > Hardware > External Displays (try both sizes to see how it looks).
- You should see your app in the Apple Watch simulator. Swipe left to see the other time zones.
You can grab the full source code for this tutorial. Note: Built for Xcode 6.3 (Swift 1.2).
If you would like to continue on and add a Glance to your watch app, read Tutorial: Building a Apple Watch Glance or add a Dynamic Notification Interface, read Tutorial: Building a Apple Watch Notification