Introduction
At WWDC 2015, Apple announced its first major update to the Apple Watch software, watchOS 2. This new update brought with it many new APIs and features for developers to take advantage of, including native apps, access to more of the Apple Watch's hardware, and better communication with the parent iOS app.
In this tutorial, I am going to be showing you how to take advantage of another new feature, the ability to create custom watch complications using the ClockKit framework. This tutorial requires that you are running Xcode 7 on OS X Yosemite (10.10) or later.
If you don't know already, Apple Watch complications are the small interface elements that display information on the watch face.
The above screenshot shows how five complications are displayed on the Modular watch face in addition to the time in the top-right corner.
1. Setting Up the Project
Create a new project in Xcode, using the watchOS > Application > iOS App with WatchKit App template.
Next, configure the project as shown below, making sure the Include Complication is checked.
Once Xcode has created your project, open the ClockKit Introduction WatchKit Extension in the Project Navigator. You will see a new Complications Configuration section as shown below.
The five checkboxes under Supported Families represent the different complication families that your app supports. In this tutorial, we are going to focus on the Modular Large family. You can uncheck the other checkboxes for now.
As stated in Apple's watchOS 2 Transition Guide, it is highly encouraged that your app supports all five families. The following image shows where these different families are used.
2. Setting Up the Complication
To teach you how to use ClockKit, you are going to create a simple complication that shows the time, name, and genre of fake TV shows on a particular station throughout the day.
To begin, open ComplicationController.swift in the ClockKit Introduction WatchKit Extension folder. Xcode has automatically created this file for you. It contains a ComplicationController
class, which conforms to and implements the CLKComplicationDataSource
protocol.
The methods associated with this protocol are how you provide data to ClockKit about the complications you want to show. The methods of the protocol contain a handler that you must call to pass data back to ClockKit.
Timelines
When providing data to ClockKit, you do so in the form of a timeline. You do this by filling up your complication's timeline with data objects that are linked to a specific point in time. These data objects are modeled by the CLKComplicationTimelineEntry
class. When a particular point in time is reached, ClockKit automatically displays the correct content for your app.
Your timeline entries are retrieved and cached by ClockKit well before the time they are intended to be displayed. This means that the data must be able to be retrieved and scheduled in advance. Each application has a limited budget of time to refresh its content in the background. In other words, complications are not a replacement for push notifications or the notification center.
The major benefit of providing data this way is to maintain an excellent user experience. The content for each complication is immediately available when the user raises her wrist. One of the other benefits of using this timeline data model is the ability to easily adopt the new Time Travel feature in watchOS 2. This is when the user turns the digital crown while looking at the watch face and the data of the complications reflects the time that has been travelled to.
In your code, under the Timeline Configuration mark, you will see four methods that are required to configure the timeline. To make this tutorial simple and easy to understand, we are only going to support forwards time travel and provide data for up to 24 hours. To do this, updated the implementation of the first three methods as shown below.
func getSupportedTimeTravelDirectionsForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimeTravelDirections) -> Void) { handler(.Forward) } func getTimelineStartDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) { handler(NSDate()) } func getTimelineEndDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) { handler(NSDate(timeIntervalSinceNow: (60 * 60 * 24))) }
The fourth method in the Timeline Configuration section, getPrivacyBehaviorForComplication(_:withHandler:)
, is used to specify whether or not you want your complication's content to be shown when the device is locked or not. The default value passed to the handler, ShowOnLockScreen
, means that the data will always be visible.
Templates
Scroll to the bottom of the ComplicationController
class and find the getPlaceHolderTemplateForComplication(_:withHandler:)
method. In this method, you create and pass a CLKComplicationTemplate
back to the handler for the data that you want to show. In this tutorial, you are going to use the CLKComplicationTemplateModularLargeStandardBody
template, which displays three lines of text. For each TV show, these three lines are going to be:
- start and end time
- name
- genre
There are quite a few templates available across the five complication families. The following image shows the available templates and highlights the one that we are going to be using in this tutorial.
Text Providers
Because space for content is limited on an watch display, even more so in the small sizes of watch face complications, you provide text-based data to ClockKit, using CLKTextProvider
objects. These providers aim to avoid truncating content, which results in a bad user experience. With these text providers, you describe your intentions about what content you want displayed and then ClockKit handles the final formatting for you.
For example, if you want to display a date in your complication, you use the CLKDateTextProvider
with a specified date and set of units (month, day, hour, etc.). That will correctly format the date for the available space. An example of this would be taking the date "Thursday, October 22" and being able to format it to:
- Thur, October 22
- Thur, Oct 22
- Oct 22
- 22
For a complete list of the text providers and templates available in ClockKit, you can look at Apple's ClockKit Framework Reference.
Now that you know about the basic templates and text providers, you are ready to create a template for your complication. In the getPlaceHolderTemplateForComplication(_:withHandler:)
method, add the following code:
func getPlaceholderTemplateForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTemplate?) -> Void) { // This method will be called once per supported complication, and the results will be cached let template = CLKComplicationTemplateModularLargeStandardBody() template.headerTextProvider = CLKTimeIntervalTextProvider(startDate: NSDate(), endDate: NSDate(timeIntervalSinceNow: 60 * 60 * 1.5)) template.body1TextProvider = CLKSimpleTextProvider(text: "Show Name", shortText: "Name") template.body2TextProvider = CLKSimpleTextProvider(text: "Show Genre", shortText: nil) handler(template) }
With this code, you create a Standard Body template for the Modular Large family and give it three text providers. The CLKSimpleTextProvider
objects should be straightforward. The CLKTimeIntervalTextProvider
takes two dates and formats them into a string, such as "10:00AM-3:30PM" or "1:00-2:45PM".
3. Testing the Complication
Now that we have configured our timeline and provided ClockKit with a template for our complication, we can finally test out the results of our work. In the top left of your Xcode window, choose your complication target and one of the available simulators. Click the play button to build and run your app.
When the Apple Watch simulator launches, you will most likely be presented with the following watch face:
To test out your complication, you need to complete a few steps.
Step 1
Press Command+Shift+2 to simulate a force touch and click on the watch face.
Step 2
Press Command+Shift+1, swipe to the right to the Modular face and click on the Customize button.
Step 3
Swipe to the right, tap the middle complication, and scroll to the bottom, using your trackpad or mouse to simulate the digital crown.
Step 4
Simulate a force touch again to get back to the watch face chooser and choose the Modular face.
Congratulations. You have just gotten you very first ClockKit complication to show up on an Apple Watch watch face. Now it's time for you to start filling it out with some actual (fake) data.
4. Providing Data to ClockKit
Before we create any timeline entries for our complication, we are first going to create a Show
struct to easily model our data and then create values of this new type. Add the following code snippet to ComplicationController.swift, before the start of the ComplicationController
class definition.
struct Show { var name: String var shortName: String? var genre: String var startDate: NSDate var length: NSTimeInterval } let hour: NSTimeInterval = 60 * 60 let shows = [ Show(name: "Into the Wild", shortName: "Into Wild", genre: "Documentary", startDate: NSDate(), length: hour * 1.5), Show(name: "24/7", shortName: nil, genre: "Drama", startDate: NSDate(timeIntervalSinceNow: hour * 1.5), length: hour), Show(name: "How to become rich", shortName: "Become Rich", genre: "Documentary", startDate: NSDate(timeIntervalSinceNow: hour * 2.5), length: hour * 3), Show(name: "NET Daily", shortName: nil, genre: "News", startDate: NSDate(timeIntervalSinceNow: hour * 5.5), length: hour) ]
As you can see, we create the Show
structure and create a static array that contains four shows. You will use this array as your complication's data source.
In the ComplicationController
class, find the getCurrentTimelineEntryForComplication(_:withHandler:)
method and add the following code to it:
func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) { // Call the handler with the current timeline entry let show = shows[0] let template = CLKComplicationTemplateModularLargeStandardBody() template.headerTextProvider = CLKTimeIntervalTextProvider(startDate: show.startDate, endDate: NSDate(timeInterval: show.length, sinceDate: show.startDate)) template.body1TextProvider = CLKSimpleTextProvider(text: show.name, shortText: show.shortName) template.body2TextProvider = CLKSimpleTextProvider(text: show.genre, shortText: nil) let entry = CLKComplicationTimelineEntry(date: NSDate(timeInterval: hour * -0.25, sinceDate: show.startDate), complicationTemplate: template) handler(entry) }
You first create a complication template, just as you did before, and fill it with content. You then create a CLKComplicationTimelineEntry
object with two parameters:
- a date
- a template
The date you specify here is where this entry will be positioned on the timeline and shown to the user. For our app, we give our timeline entry a date of fifteen minutes before the show is due to start so that it will show up on the user's watch just before the show is on.
Next, you need to provide ClockKit with all the other shows you have created for your complication. This is done in the getTimelineEntriesForComplication(_:afterDate:limit:withHandler:)
method. The limit parameter in this method is there so that a single application can't overload the ClockKit cache with data and knows exactly how many timeline entries it needs to provide.
Add the following code to the getTimelineEntriesForComplication(_:afterDate:limit:withHandler:)
method in ComplicationController.swift:
func getTimelineEntriesForComplication(complication: CLKComplication, afterDate date: NSDate, limit: Int, withHandler handler: (([CLKComplicationTimelineEntry]?) -> Void)) { // Call the handler with the timeline entries after to the given date var entries: [CLKComplicationTimelineEntry] = [] for show in shows { if entries.count < limit && show.startDate.timeIntervalSinceDate(date) > 0 { let template = CLKComplicationTemplateModularLargeStandardBody() template.headerTextProvider = CLKTimeIntervalTextProvider(startDate: show.startDate, endDate: NSDate(timeInterval: show.length, sinceDate: show.startDate)) template.body1TextProvider = CLKSimpleTextProvider(text: show.name, shortText: show.shortName) template.body2TextProvider = CLKSimpleTextProvider(text: show.genre, shortText: nil) let entry = CLKComplicationTimelineEntry(date: NSDate(timeInterval: hour * -0.25, sinceDate: show.startDate), complicationTemplate: template) entries.append(entry) } } handler(entries) }
You first create an empty array of CLKComplicationTimelineEntry
objects. You then iterate through the shows you created earlier. For each show, if it starts after the date provided by ClockKit and the limit of entries hasn't been exceeded, then you create a template and append this to the array.
At the end of this method, you call the handler with your array. Passing nil
or an empty array to the handler will tell ClockKit that you have no more data to provide and it will stop querying your CLKComplicationDataSource
object until more data is required.
With these methods in place, you are now ready to see your completed complication. Click the play button to build and run your app. When the simulator first launches, you will see your complication showing the data for the first show that you created.
If you then scroll with your trackpad or mouse to enter Time Travel mode, you will see that the other shows you created are displayed by your complication at the correct point in time.
Conclusion
In this tutorial, you learned the fundamentals of the ClockKit framework and how to create a custom watch face complication for the Apple Watch. This included the five complication families, basic templates and text providers, and timeline-based data.
In your complication, you also built in support for the watchOS 2 time travel feature and adding in new data as time progresses. As always, if you have any comments or questions, leave them in the comments below.