watchOS: Complications

Feb 7 2023 Swift 5.6, watchOS 8.5, Xcode 13

Part 1: Introduction to Complications

2. Create Your First Complication

Episode complete

Play next episode

About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 1. Explore Complications Next episode: 3. Update a Complication's Data

This video Create Your First Complication was last updated on Feb 7 2023

Explore the sample

To start, we’ll build a complication for this TideWatch app, so let’s see what we’ve got to work with.

If you build and run from this episode’s starter materials, and wait a moment, you’ll see the current tide conditions at the Point Reyes tide station in California.

You can tap the station name to pick a new location, and see the conditions there.

Even though the app is quite useful, as designed, our customers would have to open the app to find out what the current water level is.

We can put that information right on the watch face with a complication!

Complication data source

When you create a watchOS project, Xcode will generate ComplicationController.swift. For the sample project, I’ve moved it into the Complications folder, here.

The generated file will also come with a lot of boilerplate code, but the only bit that’s required is this CLKComplicationDataSource method: currentTimelineEntry(for:).

The current timeline entry

When watchOS wants to update the data displayed for your complication, it calls this method. You’re expected to return either the data to display right now or nil if you can’t provide any data.

What happens if you can’t provide a data point for the current time?

If you take a look in your extension’s Asset catalog, you’ll find a Complication folder. If you return nil from that currentTimelineEntry method, then watchOS will look in here for an appropriately named image to use, instead

For our first complication, we’ll try something that works on the simulator’s default watch face: Meridian.

It uses the .graphicCircular complication family for most configurable complications.

If the Apple Watch is showing a complication family type you don’t support, then you return nil.

guard == .graphicCircular else {
  return nil

Then, you create a complication template of the appropriate type and configure the text to display.

let template = CLKComplicationTemplateGraphicCircularStackText(
  line1TextProvider: .init(format: "Surf's"),
  line2TextProvider: .init(format: "Up!")

Notice this template takes two text providers. Each template uses different textual or graphical elements, so check the documentation for the various templates to find one that best matches your needs

Finally, return a CLKComplicationTimelineEntry that specifies the time of the data point and the template to display.

return .init(date: Date(), complicationTemplate: template)

The date specified should never be in the future, but it may be in the past.

To check out our work, switch the active scheme to TideWatch –> WatchKit –> App –> (Complication) and then build and run again.

Using the complication scheme ensures that your supported families are used without caching. The scheme will also launch the simulator directly to the watch face and give your app a small amount of background processing time.

Tap and hold on the watch face, and the editor will appear:

Note: The simulator sometimes has issues bringing up the editor. If the edit button doesn’t appear, quit and restart the simulator, or use a physical device instead.

Tap Edit, then swipe left two times so you can pick the complication you want to replace:

We’ve set up a circular complicaiton, so select any of the small circular ones to replace. Now we can see the list of complications to choose from:

Scroll until you see your app listed… but our app is not listed! So, what did we do wrong?

CLKComplicationDataSource has an optional method named complicationDescriptors().

It is only optional in the sense that your app will compile without it.

func complicationDescriptors() async -> [CLKComplicationDescriptor] {


It is not actually optional if you want your complication to be listed.

Note: The previous version of watchOS looked in Info.plist for the supported complications. That’s why the method is optional. Don’t use the Info.plist anymore, per Apple’s recommendation.

As you can see from the return type, we need to provide and array of CLKComplicationDescriptor items. Each descriptor will appear in the list of complications to choose from.

A complication descriptor needs three bits of information from you:

      identifier: ~,
      displayName: ~,
      supportedFamilies: ~

First, an identifier! Each complication you support should have a unique name. The names should be deterministic, and shouldn’t change between app launches. We’re only going to set up one, so we can use the same sort of reverse-domain naming scheme that we use for our app bundle IDs.

identifier: "com.yourcompany.TideWatch",

The displayName is what the user sees when choosing a complication from the list that your app supports.

displayName: "Tide Conditions",

And finally, an array of the families this complication supports. For us, that’s just graphic circular.

supportedFamilies: [.graphicCircular]

Build and run again. Tap and hold on the watch face, Edit… And this time, when you scroll, you’ll see your complication listed as an option to pick!

The app name displayed here is based on the Display Name set on your TideWatch WatchKit App target.

So, we’ve made some progress, there’s our complication! But what’s up with the empty circle? Why isn’t it showing the “Surf’s Up!” message we specified in the timeline?

If I select this, I can see our message on the watch face. So, what’s going on? Ahhh it’s cliffhanger! A wavehanger? I don’t really know surf lingo. In any case, we’ll solve this mystery in the next episode.