WatchKit 2: Complications

Steve Sparks

7 min read

Jul 4, 2015


WatchKit 2: Complications

Were you a watch geek before smartphones? I was.

There are a lot of nerds with a soft spot for horology. I tend towards chronographs; I love the extra functions for stopwatches, secondary time zones and time-to-speed conversions. All of those additional displays are called complications, so named because they used to be made of a clockwork, and thus each added a set of gears and a display to the mechanism.

Apple has done a pretty great job of enabling custom complications with watchOS 2. WatchOS now includes ClockKit, which has all the complication classes. The model is pretty straightforward: Your watch extension has a class that conforms to the CLKComplicationDataSource protocol. This protocol defines the methods that the complication needs in order to function.

Creating your Project with a Complication

To start adding a complication to your app, you simply tick a checkbox when creating the project:

Enable complications

Checking the “Include Complication” box adds a ComplicationController class to your project and configures the appropriate bits. When you go to view your extension target in Xcode, you’ll see checkboxes for
the five potential classes of complication:

Enable complications

Templates and Providers

Complications are populated via classes of the type CLKComplicationTemplate. There are numerous styles of template, depending on how you wish to present your data. In the next section, we’ll take a detailed look at them, but first, let’s talk about how values are transmitted.

To provide the values that are used to fill in the complication’s field, we set providers on the templates. It’s a bit more flexible than just calling setText: on the template. Instead, we hand the template a subclass of CLKTextProvider. There’s one called CLKSimpleTextProvider that is very commonly used. It has a text property, and whenever the template needs a value, it just uses that value.

There’s a text provider for dates, times, relative dates (e.g., “two weeks”) and relative times (e.g., “three hours”).

Delivering images is the job of the CLKImageProvider. The image provider has a foreground and a background image, with associated colors.

    CLKImageProvider *imageProvider = [CLKImageProvider
       imageProviderWithBackgroundImage:[UIImage imageNamed:@"circsmall_bg"]
       backgroundColor:[UIColor redColor]
       foregroundImage:[UIImage imageNamed:@"circsmall_fg"]

    CLKComplicationTemplateCircularSmallSimpleImage *tmpl =
      [[CLKComplicationTemplateCircularSmallSimpleImage alloc] init];

    tmpl.imageProvider = imageProvider;

Note that we supply two images and two color specs. Only, one of the color parameters is actually a UIColor, while the other is an enum. Both images are treated as alpha-only (that is, color is discarded), and then colorized according to your wishes. The background image may be tinted any color, but the foreground image may only match the system white or the system black.

In both of these cases, the image provider will also appropriately scale the image. You shouldn’t use that feature, however, as it’s a performance hit. It shouldn’t be a problem to simply generate your images in the correct size.

Every place where I’ve specified a size below, those sizes are in pixels. All images for Apple Watch must have the @2x modifier, too.

All The Templates That Fit, We Print!

The primary data holder in ClockKit is the CKComplicationTemplate. Each different style of complication has subclasses specific to it; as of the first beta, there were 22 subclasses, grouped into five major groups. I am going to dig in deep on only a couple of them, and we can extrapolate from there.

Modular Large

The modular large template is for this big space in the middle of the Modular watch face.

Modular large

The screen displayed here shows the Standard Body template. As you can see, it shows multiple lines of data. The first would be the header line, and then there’s “body 1” and “body 2.”

We can surmise that the code might look something like this:

    CLKComplicationTemplateModularLargeStandardBody *t = [[CLKComplicationTemplateModularLargeStandardBody alloc] init];
    t.headerTextProvider = [CLKSimpleTextProvider textProviderWithText:data[@"Waxing gibbous"]];
    t.body1TextProvider = [CLKSimpleTextProvider textProviderWithText:data[@"Moonrise 5:27PM"]];
    t.body2TextProvider = [CLKTimeIntervalTextProvider textProviderWithStartDate:[NSDate date] endDate:[NSDate dateWithTimeIntervalSinceNow:((6*60*60)+(18*60))];

Obviously there are other formatters involved—almost certainly a CLKTimeTextProvider for that 5:27PM bit—and maybe some funkiness with fonts as well. But the basic idea is here.

The large modular view has several templates. You could display a two-row, three-column grid, or a two-row, two-column grid, both with bold headers; the standard body displayed here; or a tall body, where you have a header line and then a double-height text string.

Modular Small

Modular small

The Modular clock face has the one large section and four small sections. There are seven options for layout of Modular Small complications. The highlighted complication shows the Stack Image template.

There, we place an image and then some text below it. There are a bunch of other arrangements: two rows of text, a 2×2 table or a progress ring surrounding an image or text.

Utilitarian Large

There’s only one template for this form factor: CLKComplicationTemplateUtilitarianLargeFlat. This offers a fairly long string of text across the bottom of the watch.

Utilitarian large

The template also supports adding an image of size 18×42 / 20×44. Like the other templates that take an image with their text, that image gets prepended to the left.

Utilitarian Small

This is the most commonly appearing template in the system watch faces. It shrinks into the corner as much as possible (see the temperature in the upper right), but will expand to nearly the midline of the watch to hold your data.

Utilitarian small

The template higlighted here is CLKComplicationTemplateUtilitarianSmallFlat. It has a text provider and an image provider. The image provider is optional. The template in the upper right is also flat, with no image.

Circular Small

On the Color face, all four complications are a version of the Circular Small.

Cicular small

The bottom two are probably simple image templates. For those, you have to hand the server an image to display. Those images can be up to 32×32 on the 38mm face, and 36×36 on the 42mm. Those values are in pixels, and the image file names should be @2x.

The top left shows a ring text template. All the ring templates share the same interface. They have a ringStyle, a fillFraction and either an imageProvider or a textProvider. Image providers go with image templates.

The top right shows a Stack Image template. It presents an image (32×14 or 34×16) and a bit of text below it. And of course, there is also a Stack Text template that behaves the same way.

So How Does It Work?

When you install your app with complications, its extension is launched immediately. The method getPlaceholderTemplateForComplication:withHandler: is called once for each supported complication format. Values from that method are cached for the life of the application. The simplest way to support this is to have a method that will create the appropriate template when handed a model object, and then create a placeholder model object for this method.

Once the placeholder has been delivered, the user would be able to install your complication. And if it does get installed, your extension will be awakened and -getCurrentTimelineEntryForComplication:withHandler: will be invoked. It wants a single result, the value for right now. After this gets called, the system will call -getNextRequestedUpdateDateWithHandler: to schedule the next data refresh. Finally, the method -getPrivacyBehaviorForComplication:withHandler: lets the watch know if the values need to be obscured on a locked watch.

That’s an interesting pattern, this habit of passing in the complication and a handler block. I wonder why that is? My bet is that this entire system was built to be asynchronous. We’re dealing with different processes, don’t forget. The clock app has to receive this data over some form of interprocess communication. Plus, generating the complication template might involve you sending off a request to your parent app, or maybe even the Internet. For all those reasons, this call needs to separate the reply from the request.

And that’s why ClockKit has handler blocks all over.

Time Travel: Showing Past/Future Events

If the user moves the Digital Crown, the watch snaps into Time Travel mode. There are a few methods to support Time Travel:

  • -getSupportedTimeTravelDirectionsForComplication:withHandler:
  • -getTimelineStartDateForComplication:withHandler:
  • -getTimelineEndDateForComplication:withHandler:
  • -getTimelineEntriesForComplication:beforeDate:limit:withHandler:
  • -getTimelineEntriesForComplication:afterDate:limit:withHandler:

These methods work together to specify the range of Time Travel you’re willing to support. The entries are CLKComplicationTimelineEntry objects, which contain an NSDate and a CLKComplicationTemplate.

One of the funny things about that date is that it represents the time when this template should begin to be displayed. If your template discusses a future event, like a calendar entry might, then the NSDate passed in here should be equal to (or just after) the previous calendar entry closes. If I have a meeting from 1 p.m. to 2 p.m., and then another at 4 p.m., I should see the 4 p.m. entry only after 2 p.m.

Time Travel currently supports a 72-hour range, comprising yesterday, today and tomorrow. There’s no point in supplying start or end dates, or timeline entries that exceed those limits.

Triggering a Refresh

When your app’s underlying data changes, you might want to update the complications for your watch with the new data. There’s a mechanism for that, but be warned: It is rate limited. Once you’ve triggered your limit on refreshes for the day, you can’t refresh until the following day. It sounds a bit draconian, but Apple claims this is to protect battery life. I’d be interested to see just what the limit is.

- (void)refreshComplications {
    CLKComplicationServer *server = [CLKComplicationServer sharedInstance];
    for(CLKComplication *complication in server.activeComplications) {
        [server reloadTimelineForComplication:complication];

See? Not Too Complicated!

Complications aren’t difficult to use; they’re just views of your data like any other. There’s a little more abstraction involved than in UIKit, but not too much. One of the things Apple has repeatedly driven home to us is that your interactions on the watch should be incredibly short, time-wise. What could be more convenient than a small display of your data right on the watch face?

As my traditional watches continue to rest in their cases, and my Apple watch
takes ownership of my wrist, I’m looking forward to installing some new and amazing complications. I hope this article helps you to write the next complication that tickles me!

Steve Sparks

Author Big Nerd Ranch

Steve Sparks has been a Nerd since 2011 and an electronics geek since age five. His primary focus is on iOS, watchOS, and tvOS development. He plays guitar and makes strange and terrifying contraptions. He lives in Atlanta with his family.

Speak with a Nerd

Schedule a call today! Our team of Nerds are ready to help

Let's Talk

Related Posts

We are ready to discuss your needs.

Not applicable? Click here to schedule a call.

Stay in Touch WITH Big Nerd Ranch News