Advertisement
Scroll to top

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.

Apple Watch complicationsApple Watch complicationsApple Watch complications

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.

Project templateProject templateProject template

Next, configure the project as shown below, making sure the Include Complication is checked.

Project optionsProject optionsProject options

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.

Complications ConfigurationComplications ConfigurationComplications Configuration

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.

Modular Large family choiceModular Large family choiceModular Large family choice

As stated in Apple's watchOS 2 Transition Guideit is highly encouraged that your app supports all five families. The following image shows where these different families are used.

Apple Watch complication familiesApple Watch complication familiesApple Watch complication families
Image Credit: Apple watchOS 2 Transition Guide

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.

1
func getSupportedTimeTravelDirectionsForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimeTravelDirections) -> Void) {
2
    handler(.Forward)
3
}
4
    
5
func getTimelineStartDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
6
    handler(NSDate())
7
}
8
    
9
func getTimelineEndDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
10
    handler(NSDate(timeIntervalSinceNow: (60 * 60 * 24)))
11
}

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.

Available watch complication templatesAvailable watch complication templatesAvailable watch complication templates
Image Credit: Creating Complications with ClockKit (WWDC 2015)

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:

1
func getPlaceholderTemplateForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTemplate?) -> Void) {
2
    // This method will be called once per supported complication, and the results will be cached

3
    let template = CLKComplicationTemplateModularLargeStandardBody()
4
    
5
    template.headerTextProvider = CLKTimeIntervalTextProvider(startDate: NSDate(), endDate: NSDate(timeIntervalSinceNow: 60 * 60 * 1.5))
6
    template.body1TextProvider = CLKSimpleTextProvider(text: "Show Name", shortText: "Name")
7
    template.body2TextProvider = CLKSimpleTextProvider(text: "Show Genre", shortText: nil)
8
    
9
    handler(template)
10
}

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.

Choosing the complication targetChoosing the complication targetChoosing the complication target

When the Apple Watch simulator launches, you will most likely be presented with the following watch face:

Initial watch faceInitial watch faceInitial 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.

Watch face chooserWatch face chooserWatch face chooser

Step 2

Press Command+Shift+1, swipe to the right to the Modular face and click on the Customize button.

Modular face optionModular face optionModular face option

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.

Complication pickerComplication pickerComplication picker

Step 4

Simulate a force touch again to get back to the watch face chooser and choose the Modular face.

Modular face with your complicationModular face with your complicationModular face with your complication

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.

1
struct Show {
2
    var name: String
3
    var shortName: String?
4
    var genre: String
5
    
6
    var startDate: NSDate
7
    var length: NSTimeInterval
8
}
9
10
let hour: NSTimeInterval = 60 * 60
11
let shows = [
12
    Show(name: "Into the Wild", shortName: "Into Wild", genre: "Documentary", startDate: NSDate(), length: hour * 1.5),
13
    Show(name: "24/7", shortName: nil, genre: "Drama", startDate: NSDate(timeIntervalSinceNow: hour * 1.5), length: hour),
14
    Show(name: "How to become rich", shortName: "Become Rich", genre: "Documentary", startDate: NSDate(timeIntervalSinceNow: hour * 2.5), length: hour * 3),
15
    Show(name: "NET Daily", shortName: nil, genre: "News", startDate: NSDate(timeIntervalSinceNow: hour * 5.5), length: hour)
16
]

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:

1
func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {
2
    // Call the handler with the current timeline entry

3
    
4
    let show = shows[0]
5
    let template = CLKComplicationTemplateModularLargeStandardBody()
6
    
7
    template.headerTextProvider = CLKTimeIntervalTextProvider(startDate: show.startDate, endDate: NSDate(timeInterval: show.length, sinceDate: show.startDate))
8
    template.body1TextProvider = CLKSimpleTextProvider(text: show.name, shortText: show.shortName)
9
    template.body2TextProvider = CLKSimpleTextProvider(text: show.genre, shortText: nil)
10
    
11
    let entry = CLKComplicationTimelineEntry(date: NSDate(timeInterval: hour * -0.25, sinceDate: show.startDate), complicationTemplate: template)
12
    handler(entry)
13
}

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:

1
func getTimelineEntriesForComplication(complication: CLKComplication, afterDate date: NSDate, limit: Int, withHandler handler: (([CLKComplicationTimelineEntry]?) -> Void)) {
2
    // Call the handler with the timeline entries after to the given date

3
   
4
    var entries: [CLKComplicationTimelineEntry] = []
5
    
6
    for show in shows
7
    {
8
        if entries.count < limit && show.startDate.timeIntervalSinceDate(date) > 0
9
        {
10
            let template = CLKComplicationTemplateModularLargeStandardBody()
11
            
12
            template.headerTextProvider = CLKTimeIntervalTextProvider(startDate: show.startDate, endDate: NSDate(timeInterval: show.length, sinceDate: show.startDate))
13
            template.body1TextProvider = CLKSimpleTextProvider(text: show.name, shortText: show.shortName)
14
            template.body2TextProvider = CLKSimpleTextProvider(text: show.genre, shortText: nil)
15
            
16
            let entry = CLKComplicationTimelineEntry(date: NSDate(timeInterval: hour * -0.25, sinceDate: show.startDate), complicationTemplate: template)
17
            entries.append(entry)
18
        }
19
    }
20
    
21
    handler(entries)
22
}

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.

Complication showing first showComplication showing first showComplication showing first show

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.

Time travelTime travelTime travel

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.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.