Home iOS & Swift Tutorials

Complications for watchOS with SwiftUI

Learn how to create complications in SwiftUI that will accelerate your development productivity, provide delightful glanceable displays and give your users a single-tap entry point with which to launch your watchOS app.

5/5 2 Ratings

Version

  • Swift 5, iOS 14, Xcode 12

Users can interact with an Apple Watch in many ways. Some of these interactions are haptic signals like a short tap on the wrist to signal an event. Other interactions are more complex involving a few taps from the wearer.

Say you start a run, get 100 feet down the road and then remember to start a workout. Without a paired phone to power Siri, you need to click the Digital Crown, find the Workout app, choose the Outdoor Run workout and then start a workout. Although that’s only a few seconds of effort, it can feel cumbersome.

That’s where complications come in. With complications, you can give watch wearers a shortcut to their most-used apps.

In this tutorial, you’ll learn about:

  • What watchOS complications are.
  • Complications that use SwiftUI.
  • Using SwiftUI previews to quickly iterate on complications.
  • Controlling complications that are tinted by the watch face’s tint color.
  • Providing real data to complications.
  • Complications that use providers.
Note: This tutorial assumes you have a basic understanding of SwiftUI and access to Xcode 12.2 or higher. You don’t need a physical watch to do this tutorial since you’ll spend most your time in the SwiftUI canvas or simulator.

Getting Started

Use the Download Materials link at the top or bottom of the tutorial to get the starter project. WhatsNext is a scheduling app that tells you how much time you have left until your next event.

The app already has an associated Watch app. In this tutorial, you’ll add some complications to the Watch app.

Unzip the archive and open the starter folder. Then open the Xcode project WhatsNext.xcodeproj:

starter project

The starter project has a data model which consists of the struct Appointment and a manager class AppointmentData. Appointment supplies a list of dummy data in dummyData(). Feel free to change the data or add new items.

First, select the iOS Simulator you want to use. Choose iPhone 12 Pro Max from the target selector:

select iOS device

In the Project navigator, locate WhatsNext ▸ Views. You’ll find the beginnings of the UI for the app. There’s not much here except a simple ListView in AppointmentList but that’s enough to work with for now.

Open ContentView.swift. Then open the SwiftUI canvas with the control on the top-right or the key combination Command-Option-Return:

show canvas

Your canvas is paused. Click Resume at the top of the canvas to tell the preview to reload. Alternatively, you can use the key combination Command-Option-P:

resume canvas preview

In a short amount of time, the preview appears:

iOS preview

Now select the simulator you’ll use for the Watch app. Use the scheme WhatsNextWatch and Apple Watch Series 6 – 44mm simulator.

scheme for watchOS

Note: You can pick the 40mm simulator if you have smaller wrists.

In the Project navigator, locate WhatsNextWatch Extension and select ContentView.swift. Like you did just now for the main app, resume the canvas and you’ll see the preview:

watchOS preview

The two apps look similar on both iOS and watchOS. They both use the same View code with a small tweak to font size between the two platforms. SwiftUI lets you easily unify your UI design across platforms.

Creating Complications

Before Xcode 12, creating a complication involved enduring a long Change/Build/Test cycle. Now you can use the SwiftUI canvas to preview your complications.

In this section, you’ll create some complications using SwiftUI. But first, there’s a little background to cover.

What is a Complication

A complication lives on your watch’s home screen, displays a small amount of information and provides direct access to your Watch app with a tap.

For example, on the Solar Dial face below there are four complications. Clockwise from top left:

  1. Timer: You don’t want to melt the coffee machine again.
  2. Date: What day is it really? Who can tell?
  3. Dawn Patrol: So you know the best time for a surf.
  4. Temperature: The cats aren’t kidding when they ask you to put the fire on.

sample watch face

When you create a watch app, you need to think about what interactions you want your customers to have with their watches. What does your app do and how does it apply to the concepts of state change or the flow of time?

There are different types of complications, grouped into families. Take a look at those now.

Complication Families

Complications are arranged into families. There is an enumeration called CLKComplicationFamily which collates these families. A family describes where on the Home screen, and how big, a complication appears. It’s your job, or your designer’s job, to create complications that use the available space well.

You might decide to have a complication for multiple different families. For example, observe the Dawn Patrol complication in a Graphic Corner context at bottom right:

graphic corner complication

That’s an effective use of space. It shows high/low tide times and current tide. Now observe the same app in a Modular Large position:

modular large complication

There’s a lot more space to use, which gives you some graphic freedom. In this case, you get a graph!

Theory done, time to complicate things with some coding!

Creating a SwiftUI Complication

To make iteration quick and easy, you’ll use the SwiftUI preview canvas to represent the Appointment data from the app as complications. You’ll already be familiar with this process if you’ve written iOS or macOS apps using SwiftUI.

In the Project navigator, select and expand WhatsNextWatch Extension. Use the key combination Command-N to create a new file in that group. Select SwiftUI View from the template window and then click Next:

file template picker

Name the file ComplicationViews and ensure it’s added to the WhatsNextWatch Extension target. Then click Create to complete adding the file:

name file

In ComplicationViews.swift, add this module import below import SwiftUI:

import ClockKit

You need both of these modules to build complications and display them as a preview.

Then insert this code below the definition of ComplicationViews:

// 1
struct ComplicationViewCircular: View {
  // 2
  @State var appointment: Appointment

  var body: some View {
    // 3
    ZStack {
      ProgressView(
        "\(appointment.rationalizedTimeUntil())",
        value: (1.0 - appointment.rationalizedFractionCompleted()),
        total: 1.0)
        .progressViewStyle(
          CircularProgressViewStyle(tint: appointment.tag.color.color))
    }
  }
}

Here’s what that code does:

  1. Define a new struct which will be your circular complication.
  2. Pass in an Appointment instance, which is state for the view, since the view will display information about the appointment.
  3. The body of the complication is simple a ZStack with a ProgressView which uses the appointment values to set it up.

Now, locate ComplicationViews_Previews at the bottom of the file. Replace the body of var previews with:

// 1
Group {
  // 2
  CLKComplicationTemplateGraphicCircularView(
    ComplicationViewCircular(
      appointment: Appointment.oneDummy(offset: Appointment.oneHour * 0.5)
    )
  // 3
  ).previewContext()
}

While this is only a few lines of code, it does lot of heavy lifting:

  1. You construct a Group so you can present many previews at once later.
  2. Then, you instantiate a CLKComplicationTemplateGraphicCircularView. Templates are a declarative way of specifying the complication’s view. This template takes one argument which is a View. In your case, an instance of ComplicationViewCircular which you just added, created with a dummy appointment.
  3. Most importantly, you ask the template for a previewContext. This displays the watch face that’s appropriate to the template.

That is the complicated part (pun intended), now it’s time to preview it.

Previewing the complication

Now, ensure the Scheme is still set to use a Watch simulator. If you’ve it set to a phone simulator the canvas, won’t load:

scheme for watchOS

Display and resume the SwiftUI canvas to see some minor magic courtesy of Xcode 12. This might take a while on the first build.

You can see a yellow-tinted progress view with 29 minutes until the next appointment. The progress is calculated against a value of 60 minutes.

corner circular complication

Now, in ComplicationViews_Previews locate:

Appointment.oneDummy(offset: Appointment.oneHour * 0.5)

Change the declaration to:

Appointment.oneDummy(offset: Appointment.oneHour * 2)

You’ll see the preview change to this:

alternate circular

This is the power of the SwiftUI canvas brought to watchOS development. You get instant results for your changes.

Note: You may notice your computer working quite hard to render the Watch previews. Don’t panic when it sounds like your computer is getting ready to take off.

The next step in your tour of complications is to take a look at another family of complications, one that you actually saw earlier in the tutorial.

Corner Circular Complications

Now you’re going to look at the CLKComplicationTemplateGraphicCornerCircularView template. This template is used for the four corners of the watch face that you saw earlier, i.e. this one:

sample watch face

The interesting thing about this one is that they can be a tinted with a color. In this section, you’ll learn how to adjust your views to cope with this tinted rendering environment.

In ComplicationViews.swift, add this code underneath the ComplicationViewCircular struct:

// 1
struct ComplicationViewCornerCircular: View {
  // 2
  @State var appointment: Appointment

  var body: some View {
    // 3
    ZStack {
      Circle()
        .fill(Color.white)
      Text("\(appointment.rationalizedTimeUntil())")
        .foregroundColor(Color.black)
      Circle()
        .stroke(appointment.tag.color.color, lineWidth: 5)
    }
  }
}

Here’s what that code does:

  1. Create a new view specifically for this type of complication.
  2. Again, you will need the appointment to be passed in.
  3. The view is a ZStack with a circle that’s filled white at the bottom, followed by text showing the time until the appointment is due, followed by a circle which is stroked with the appointment’s color.

Next, add this code inside the Group of ComplicationViews_Previews:

CLKComplicationTemplateGraphicCornerCircularView(
  ComplicationViewCornerCircular(
    appointment: Appointment.dummyData()[1])
).previewContext(faceColor: .red)

You now have a Group with two template declarations inside.

You’ve instantiated CLKComplicationTemplateGraphicCornerCircularView. Again, this template takes one View as an argument. Now the previewContext has a faceColor.

Resume the canvas if needed. Now you have two different watch faces on display. The lower one is a red tinted face with your complication at top left:

graphic corner circular

Huh. You set the color of the stroked circle to be the appointment’s color! But here it’s showing as gray! What’s going on?! We need that color back.

Bringing the Color Back

In this complication family, watchOS takes the grayscale value of any colors to represent them in a tinted environment. You can see your complication has a washed-out quality. How can you add some pop back into the view?

When the system tints the image, you need to distinguish the foreground and background. You can provide a tint as a SwiftUI view-modifier.

Find the body property in ComplicationViewCornerCircular and replace the ZStack with:

ZStack {
  Circle()
    .fill(Color.white)
  Text("\(appointment.rationalizedTimeUntil())")
    .foregroundColor(Color.black)
    .complicationForeground()
  Circle()
    .stroke(appointment.tag.color.color, lineWidth: 5)
    .complicationForeground()
}

You have added complicationForeground() view-modifier to the Text and second Circle instances. This makes watchOS consider these views as foreground elements and, therefore, tint them with the face color. Resume the canvas and you’ll see the result:

complication with face color tint

Now that you know about tinting a complication based on the watch face’s tint color, it’s time to learn a bit about how you can handle complications when they might be used in both a tinted environment and a full-color environment.

Using the Runtime Rendering State

What happens when you want to render your complication different ways for a tinted face and a full-color face. Well, you can ask EnvironmentValues for the current state.

Add this code to ComplicationViewCornerCircular below @State var appointment: Appointment:

@Environment(\.complicationRenderingMode) var renderingMode

ClockKit declares ComplicationRenderingMode and has two values: .tinted and .fullColor. You can use these values to choose a rendering style.

In ComplicationViewCornerCircular locate the first Circle in the ZStack:

Circle()
  .fill(Color.white)

Then replace that code with this switch statement:

switch renderingMode {
case .fullColor:
  Circle()
    .fill(Color.white)
case .tinted:
  Circle()
    .fill(
      RadialGradient(
        gradient: Gradient(colors: [.clear, .white]),
        center: .center,
        startRadius: 10,
        endRadius: 15))
@unknown default:
  Circle()
    .fill(Color.white)
}

Finally, add this code to the Group in ComplicationViews_Previews:

CLKComplicationTemplateGraphicCornerCircularView(
  ComplicationViewCornerCircular(
    appointment: Appointment.oneDummy(offset: Appointment.oneHour * 3.0))
).previewContext()

Now you have a third face in your canvas previews that displays the full-color version of the complication.

Resume the canvas. You’ll see that .tinted uses a RadialGradient:

complication with radial gradient color

While for .fullColor you see the original white fill:

complication with white fill

Now you know the basics of creating SwiftUI based complications. To summarize:

  1. Create a SwiftUI View.
  2. Place that View in a CLKComplicationTemplate.
  3. Profit?

You’ve learned how to show a preview of your complication in the canvas. You’ll create more complications later, but now it’s time to find out how to use your complications in a running app.

Working With the Complication Data Source

Open ComplicationController.swift. This is a CLKComplicationDataSource, which vends instances of CLKComplicationTemplate to watchOS on demand.

Like most data source patterns, there are questions asked of the data source:

  • What’s the time of the last known event?
  • What’s happening right now?
  • And what’s going to happen in the future?

It’s your job to answer those questions!

You answer the first question, What’s the time of the last known event?, in getTimelineEndDate(for:withHandler:).

First, add this property at the top of ComplicationController:

let dataController = AppointmentData(appointments: Appointment.dummyData())

AppointmentData acts as a manager for your list of Appointment objects.

Then, replace everything inside getTimelineEndDate(for:withHandler:) with:

handler(dataController.orderedAppointments.last?.date)

You supply the date of the last Appointment on your list.

Next, you answer the question What’s happening right now? in getCurrentTimelineEntry(for:withHandler:). But first, you need to set up a little help.

Add this import to the top of ComplicationController.swift:

import SwiftUI

Then add this extension to the end of the file:

extension ComplicationController {
  func makeTemplate(
    for appointment: Appointment,
    complication: CLKComplication
  ) -> CLKComplicationTemplate? {
    switch complication.family {
    case .graphicCircular:
      return CLKComplicationTemplateGraphicCircularView(
        ComplicationViewCircular(appointment: appointment))
    case .graphicCorner:
      return CLKComplicationTemplateGraphicCornerCircularView(
        ComplicationViewCornerCircular(appointment: appointment))
    default:
      return nil
    }
  }
}

In this method, you supply the correct template based on the CLKComplicationFamily of the CLKComplication requesting a template. For now, there’s only the two you created earlier, but you’ll add more to this method later.

Now, head to getCurrentTimelineEntry(for:withHandler:). Replace the supplied code inside with:

if let next = dataController.nextAppointment(from: Date()),
  let template = makeTemplate(for: next, complication: complication) {
  let entry = CLKComplicationTimelineEntry(
    date: next.date,
    complicationTemplate: template)
  handler(entry)
} else {
  handler(nil)
}

Here you construct a CLKComplicationTimelineEntry for the current event which represents a single moment along the timeline of things happening in your app.

Each event in your app may have a corresponding CLKComplicationTimelineEntry object with a template.

Data of Future Past

The third question the data source answers is What’s going to happen in the future? in getTimelineEntries(for:after:limit:withHandler).

Replace the code inside getTimelineEntries(for:after:limit:withHandler) with:

// 1
let timeline = dataController.appointments(after: date)
guard !timeline.isEmpty else {
  handler(nil)
  return
}

// 2
var entries: [CLKComplicationTimelineEntry] = []
var current = date
let endDate = (timeline.last?.date ?? date)
  .addingTimeInterval(Appointment.oneHour)

// 3
while (current.compare(endDate) == .orderedAscending) && 
  (entries.count < limit) {
  // 4
  if let next = dataController.nextAppointment(from: current),
    let template = makeTemplate(for: next, complication: complication) {
    let entry = CLKComplicationTimelineEntry(
      date: current, 
      complicationTemplate: template)
    entries.append(entry)
  }
  // 5
  current = current.addingTimeInterval(5.0 * 60.0)
}

// 6
handler(entries)

Here's what that code does:

  1. Obtain the timeline of upcoming appointments from the data controller.
  2. You're going to create an array of timeline entries spaced at five minute intervals. So create an array to put them in and also set up some variables that will be useful shortly.
  3. Iterate over no more than one hour's entries, limited by what watchOS requests.
  4. Obtain the next appointment, make a complication template for it and add it to the list of entries.
  5. Skip forward 5 minutes.
  6. Finally, hand the entries to the watchOS handler.

The reason for creating an entry spaced at each five minute interval is that it allows you to tell watchOS you want the complication updated automatically every five minutes. If you don't need the periodic update for your complication, then you can return one CLKComplicationTimelineEntry for each event in your timeline.

The following diagram shows how there are entries every five minutes, where each entry will show the next upcoming event. So all entries before "Spin Class" would show the data for "Spin Class" but would, of course, have a different time remaining.

Complication Timeline Entries

By implementing these three CLKComplicationDataSource methods, you've done the minimum you need to get your complication running.

Now, it's time to get your complication running on the simulator and see it in action!

Running Complications on the Simulator

Build and run the WhatsNextWatch target on Apple Watch Series 6 44mm. Then click the Digital Crown. Click and hold the Home screen to show the edit mode.

If Meridian isn't the face shown then swipe by clicking and dragging across the face to change faces:

watch face editing mode

Now click edit to show the face editor. Then swipe across on the face until the complication editor for the face appears:

complication editor UI

Next click one of the slots to show the complication picker. Click WhatsNext to select it:

selecting the complication

Now click the Digital Crown twice to return to the Home screen. Your complication will appear showing the time until next appointment in your list:

edit face final state

Click the complication to show the running Watch app. Neat! :]

You've seen a couple of different complication types. Now it's time to learn about some more.

Making More Complications

So far, you've learned how to preview your complications and use them in a real watchOS app. Now it's time to play with larger complications that provide more space.

Reacting to a Rectangle

The first template you'll create in this section is CLKComplicationTemplateGraphicRectangularFullView.

Open ComplicationViews.swift and add this above ComplicationViews_Previews:

struct ComplicationViewRectangular: View {
  @State var appointment: Appointment

  var body: some View {
    HStack(spacing: 10) {
      ComplicationViewCircular(appointment: appointment)
      VStack(alignment: .leading) {
        Text(appointment.name)
          .font(.title)
          // 1
          .minimumScaleFactor(0.4)
          .lineLimit(2)
          .multilineTextAlignment(.leading)
        HStack(spacing: 4.0) {
          Spacer()
          Text("at")
          // 2
          Text(appointment.date, style: .time)
        }
        .font(.footnote)
        // 3
        .complicationForeground()
      }
    }
    .padding()
    .background(
      RoundedRectangle(cornerRadius: 10.0)
        .stroke(lineWidth: 1.5)
        .foregroundColor(appointment.tag.color.color)
        .complicationForeground())
  }
}

This View is a composition of ComplicationViewCircular and some text. There are a few interesting things to note:

  1. You use minimumScaleFactor and lineLimit to control how the title text shrinks to fit content.
  2. Notice the Text(appointment.date, style: .time). Here you use one of the built-in time formatter types to display the time at which the appointment occurs. You'll explore other formatter types soon.
  3. You use .complicationForeground() to provide display hints that you learned about earlier.

Now add these two previews to the Group in ComplicationViews_Previews:

CLKComplicationTemplateGraphicRectangularFullView(
  ComplicationViewRectangular(
    appointment: Appointment.dummyData()[2])
).previewContext()
CLKComplicationTemplateGraphicRectangularFullView(
  ComplicationViewRectangular(
    appointment: Appointment.oneDummy(offset: Appointment.oneHour * 0.25))
).previewContext(faceColor: .orange)

This adds previews for the tinted and full color versions at the same time:

large rectangular complication

That was pretty quick, right? Once again, you can see the power of SwiftUI.

Circling a Problem

Are you thinking how easy this is? What's the catch?

The catch is that there are things you shouldn't put in a SwiftUI based complication. You'll explore the topic of forbidden items as you build your final SwiftUI based complication for CLKComplicationTemplateGraphicExtraLargeCircularView.

First, inside ComplicationViews.swift, add the following above ComplicationViewCircular:

// 1
struct CircularProgressArc: Shape {
  @State var progress: Double = 0.5

  func path(in rect: CGRect) -> Path {
    var path = Path()
    let limit = 0.99
    let halfarc: Double = max(0.01, min(progress, limit)) * 180.0
    path.addArc(
      center: CGPoint(x: rect.midX, y: rect.midY),
      radius: rect.width / 2,
      startAngle: .degrees(90 - halfarc),
      endAngle: .degrees(90 + halfarc),
      clockwise: true)
    return path
  }
}

// 2
struct ProgressArc<S>: ProgressViewStyle where S: ShapeStyle {
  // 3
  var strokeContent: S
  var strokeStyle: StrokeStyle

  init(
    _ strokeContent: S,
    strokeStyle style: StrokeStyle = 
      StrokeStyle(lineWidth: 10.0, lineCap: .round)
  ) {
    self.strokeContent = strokeContent
    self.strokeStyle = style
  }

  // 4
  func makeBody(configuration: Configuration) -> some View {
    CircularProgressArc(progress: configuration.fractionCompleted ?? 0.0)
      .stroke(strokeContent, style: strokeStyle)
      .shadow(radius: 5.0)
  }
}

Here's what this code does:

  1. Create a Shape object which can be used to draw an arc retreating from both sides of the bottom of a circle.
  2. Create a custom ProgressViewStyle which can be applied to a ProgressView instance to style it however you wish.
  3. There are two properties on this object. The strokeContent is any ShapeStyle and will be used to stroke the progress. This could be a Color for example. The stokeStyle indicates the style of stroke, e.g. the width of the line.
  4. The makeBody(configuration:) call returns an instance of CircularProgressArc you created above to tell the ProgressView how to draw itself.

Next, add this code above ComplicationViews_Previews:

struct ComplicationViewExtraLargeCircular: View {
  // 1
  @State var appointment: Appointment

  var body: some View {
    // 2
    ZStack(alignment: .center) {
      // 3
      Circle()
        .foregroundColor(appointment.tag.color.color)
      ProgressView(
        value: appointment.rationalizedFractionCompleted())
        .progressViewStyle(ProgressArc(Color.white))
        .complicationForeground()

      // 4
      ScrollView {
        VStack(alignment: .center, spacing: 3.0) {
          // 5
          Text("In \(Text(appointment.date, style: .relative))")
            .font(.footnote)
            .minimumScaleFactor(0.4)
            .lineLimit(2)
          Text(appointment.name)
            .font(.headline)
            .minimumScaleFactor(0.4)
            .lineLimit(2)
          Text("at \(Text(appointment.date, style: .time))")
            .font(.footnote)
        }
        .multilineTextAlignment(.center)
        .foregroundColor(.black)
        .complicationForeground()
      }
    }
    .padding([.leading, .trailing], 5)
  }
}

This code adds another view to the collection you're growing. In this one:

  1. Once again the Appointment is state for the view.
  2. There is a ZStack again to arrange all the items in a stack, containing:
  3. A Circle and a ProgressView. The ProgressView has the style applied to it which you created just now.
  4. A ScrollView which has in it a VStack with the information about the appointment

Almost done. Add these two views to the Group in ComplicationViews_Previews:

CLKComplicationTemplateGraphicExtraLargeCircularView(
  ComplicationViewExtraLargeCircular(
    appointment: Appointment.oneDummy(offset: Appointment.oneHour * 0.2))
).previewContext()
CLKComplicationTemplateGraphicExtraLargeCircularView(
  ComplicationViewExtraLargeCircular(
    appointment: Appointment.dummyData()[2])
).previewContext(faceColor: .blue)

Resume your canvas. Notice that you can't see the complication even though you can see the two new faces, and the code builds without error. Curious! You'll find out why next.

failing complication render

Correcting Course

So why doesn't that complication work? Open ComplicationController.swift. In makeTemplate(for:complication:) add this case to the switch statement above default::

case .graphicExtraLarge:
  return CLKComplicationTemplateGraphicExtraLargeCircularView(
    ComplicationViewExtraLargeCircular(
      appointment: appointment))

Here you return an instance of CLKComplicationTemplateGraphicExtraLargeCircularView for the .graphicExtraLarge complication family.

Build and run. When the app opens, return to the Home screen. Then put the Home screen into editing mode again with a long press.

Swipe left until you reach the New screen:

add new face screen

Click + then select X-LARGE from the list.

select x large face

Now, click the face to select it. Then long press to return to edit mode. Edit the face and select the WhatsNext complication before returning to the Home screen.

failed state

That's a sad watch face!

You'll see this warning in the Runtime section of the Issue navigator and in the console:

runtime warning in navigator

If you open ComplicationController.swift, you'll the same warning on the handler(_:) call in getCurrentTimelineEntry(for:withHandler:).

Open ComplicationViews.swift and locate ComplicationViewExtraLargeCircular. Remove the ScrollView by deleting the ScrollView { and its closing }. Do not remove the views within the ScrollView.

Resume your preview canvas. Now you can see the complication:

large circular face corrected

So just a reminder that you cannot put all SwiftUI views into a complication. It's limited to simple views which don't require user interaction, like text and shapes. But you can do a lot with those! Next up, you're going to take a look at another type of complication which doesn't use a SwiftUI view.

Beyond SwiftUI View Complications

In this section you'll build a complication to support the .utilitarianLarge family. This family uses the CLKComplicationTemplateUtilitarianLargeFlat template, which appears as a single line across the width of the watch with text and an optional image, like so:

where do utilitarian large complications appear

Open ComplicationController.swift. Then, add this code at the bottom of the file:

extension ComplicationController {
  // 1
  func makeUtilitarianLargeFlat(appointment: Appointment) 
  -> CLKComplicationTemplateUtilitarianLargeFlat {
    // 2
    let textProvider = CLKTextProvider(
      format: "\(appointment.name) in \(appointment.rationalizedTimeUntil())")

    // 3
    if let bgImage = UIImage.swatchBackground(),
      let fgImage = UIImage.swatchForeground(name: appointment.tag.name),
      let onePiece = UIImage.swatchOnePiece(name: appointment.tag.name) {
      // 4
      let imageProvider = CLKImageProvider(
        onePieceImage: onePiece,
        twoPieceImageBackground: bgImage,
        twoPieceImageForeground: fgImage)
      // 5
      let complication = CLKComplicationTemplateUtilitarianLargeFlat(
        textProvider: textProvider,
        imageProvider: imageProvider)
      return complication
    } else {
      // 6
      let complication = CLKComplicationTemplateUtilitarianLargeFlat(
        textProvider: textProvider)
      return complication
    }
  }
}

Here's what this does:

  1. makeUtilitarianLargeFlat(appointment:) is a method that takes an Appointment and returns a CLKComplicationTemplateUtilitarianLargeFlat.
  2. Create a CLKTextProvider, which is essentially an object that can vend text when it's needed.
  3. Create three images using methods in UIImage+Lozenge.swift. These images will be used in the complication view to display a little lozenge shape with the first three letters of the appointment's tag name in it.
  4. If the images created successfully, create a CLKImageProvider, which will vend an image as needed to display in the complication. You can read more about this initializer on Apple's website.
  5. Then, create a CLKComplicationTemplateUtilitarianLargeFlat with the text and image providers you created.
  6. If the images failed to create, simply create a CLKComplicationTemplateUtilitarianLargeFlat with only the text.

Still in ComplicationController.swift, locate makeTemplate(for:complication:). Then add this case to switch above default::

case .utilitarianLarge:
  return makeUtilitarianLargeFlat(appointment: appointment)

Build and run to see the complication rendered. Add the face Activity Digital. Then edit the face to select the WhatsNext complication:

utilitarian large rendered

Congrats! You've made a complication that uses providers rather than SwiftUI views!

And that's it! You've completed the tutorial. That wasn't complicated now was it!? Pun intended. :]

Where To Go From Here

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

You've seen that creating complications with SwiftUI is a simple and rapid process that lets you quickly iterate upon your designs. When you build a set of complications for your app, you need to consider the following elements:

  • Observe the concepts of simplicity and frugality.
  • Try to stick to shapes, lines, text and images.
  • Ensure your running code isn't too CPU expensive. The watch hardware isn't the place to do expensive computation. If you need complex algorithms performed, do that work on the phone or back end and send the result to the watch for display.

One thing you might want to try out is working out how to get the complication picker to show something better than two dashes. Look at getLocalizableSampleTemplate(for:withHandler:) and, if you need a hint, check out the final project which shows how to do it.

If your complication is too power-hungry, watchOS will throttle it back, and your customers will receive an unpredictable experience.

These WWDC 2020 videos are good background on the topic of complications.

I hope you enjoyed this tutorial. If you have any questions or comments, please join the discussion below.

Average Rating

5/5

Add a rating for this content

2 ratings

More like this

Contributors

Comments