Chapters

Hide chapters

watchOS With SwiftUI by Tutorials

First Edition · watchOS 8 · Swift 5.5 · Xcode 13.1

Section I: watchOS With SwiftUI

Section 1: 16 chapters
Show chapters Hide chapters

11. Tinted Complications
Written by Scott Grosch

The default setting for the Apple Watch shows complications in full-color. When selecting and populating a watch face, the user may instead choose to select a tint color. If they select a tint, all elements on the watch face will change to honor the selected tint color.

Full-color

Open Happy.xcodeproj from this project’s starter materials. Then press Control‑0 (that’s a zero) to select the Happy WatchKit App (Complication) scheme. This chapter focuses solely on the complication, not the app itself, so switching to the complication scheme makes sense instead of running the app.

Build and run. Once launched, long-press the watch face to bring up the face editor:

Then swipe left to get to the add new face screen:

Tap Add, then scroll to the bottom of the list to select the X-Large face:

Once you’ve tapped Add, the color selection screen will appear. Scroll to the MULTICOLOR choice if it’s not already selected:

Swipe left again to see the empty complications screen.

After tapping the empty square, scroll down to the Happy app:

Tap to select it, then press the home button twice.

Looks amazing, right? :]

What happens when the watch face is tinted?

Desaturation

By default, if the user selects a tint color for the watch face, watchOS will desaturate the full-color image. Desaturation is the process of making colors more muted. By adding more black or white to the image, the colors become less vibrant. The more you desaturate an image, the more color you remove.

To see an example of what that means, select a tint color by following steps similar to those you performed when adding the watch face:

  1. Long-press the watch face.
  2. Tap Edit.
  3. Swipe right to the color screen.
  4. Scroll through the colors.
  5. Return to the Home screen by pressing the Digital Crown twice.

Scrolling through the colors lets you see what the image would look like for each tint color. Depending on the image you’re using, desaturation might work just fine. However, if your colors are too similar to each other in hue, the desaturation might not work as the image blends into itself too much.

Layered images

When you require more control over how the image looks while tinted, you can split it into two separate images. For example, consider the smiley-face image you’re using to have two separate parts. The circular part of the face, in green, is the background. The eyes and mouth are then the foreground.

Open Assets.xcassets, and you’ll see the Full image currently in use. There are also two other images. Click Background, and you’ll see it’s simply the green circle from the face. Next, click eyesAndMouth, and you’ll see…wait for it…the eyes and mouth. :]

Open ComplicationController.swift and replace everything in currentTimelineEntry(for:), except for the return statement, with:

// 1
guard
  let full = UIImage(named: "Full"),
  let background = UIImage(named: "Background"),
  let eyesAndMouth = UIImage(named: "eyesAndMouth")
else {
  fatalError("Images are missing from the asset catalog.")
}

let template = CLKComplicationTemplateGraphicExtraLargeCircularImage(
  imageProvider: CLKFullColorImageProvider(
    // 2
    fullColorImage: full,
    // 3
    tintedImageProvider: .init(
      // 4
      onePieceImage: full,
      // 5
      twoPieceImageBackground: background,
      // 6
      twoPieceImageForeground: eyesAndMouth
    )
  )
)

Here’s what happening:

  1. First, you create the images you’ll use. The fatalError makes sense as you want to catch typos before shipping the app.
  2. Just like before, you specify the full-color image to display.
  3. Using the two-parameter initializer to CLKFullColorImageProvider, you can now specify exactly what to do when tinting.
  4. Specify the same full-color image again.
  5. Then specify that the colored circle is the background layer.
  6. The eyes and mouth will be the foreground layer.

watchOS will apply tinting based on the two separate layers you provide. Sometimes it will tint the foreground layer. Other times it will tint the background layer. Which tint receives the user’s preferred color is dependent on the type of complication family you select.

Build and run again. After the app starts and the simulator updates, you’ll see the effect of your changes:

As you can see, when using a CLKComplicationTemplateGraphicExtraLargeCircularImage template, watchOS will apply the tint color to the background. watchOS chooses the foreground color.

When using layered images, watchOS only uses the opacity. It completely ignores the color.

SwiftUI complications

SwiftUI graphic complication views also support tinting but with a different syntax.

Create a new SwiftUI View file called HappyComplication.swift and replace the contents of the body with the full-color image:

Image("Full")
  .resizable()
  .aspectRatio(contentMode: .fit)

Note: If the Canvas isn’t showing, press Alt‑Command‑Enter to bring it up.

Desaturation

By default, SwiftUI complications will be desaturated, like the non-SwiftUI versions. Add ClockKit to the top of the file:

import ClockKit

Then replace the previews view with code to show as a complication:

CLKComplicationTemplateGraphicExtraLargeCircularView(
  HappyComplication()
)
  .previewContext()

Looks great, but it’s not tinted. The previewContext will take a faceColor parameter to support tinting. The parameter isn’t a Color, though. It’s a CLKComplicationTemplate.PreviewFaceColor enum.

Why does that matter? You can use a loop to see multiple versions at once. Replace your preview code again with this:

// 1
Group {
  // 2
  ForEach(CLKComplicationTemplate.PreviewFaceColor.allColors) {
    CLKComplicationTemplateGraphicExtraLargeCircularView(
      HappyComplication()
    )
      // 3
      .previewContext(faceColor: $0)
  }
}

The previous code performs these actions:

  1. Using a Group lets you display multiple watch faces at once.
  2. Using the .allColors enumeration value, you loop through each predefined color.
  3. You pass the face color to the preview context to tint to that color.

A couple of years later, once the Canvas has finally refreshed, you’ll see your complication first in full-color and then tinted to the seven pre-defined preview colors. Using the enum value is an excellent way to ensure your complication looks good across multiple colors.

Layered

To use layered tinting, change the image name from Full to eyesAndMouth. Next, add another modifier to the Image like so:

.complicationForeground()

By adding that modifier, you let watchOS know that it should consider the image as the foreground layer of the tinting. At this point, your preview will show all the mouths, except the first, in white.

What about the rest of the face? You have to create the background layer as well. Wrap the Image with:

ZStack {
  Circle()

  // The Image here
}

When the Canvas refreshes the preview, you’ll once again have a “body” for your face, with the proper background color, even though you didn’t specify a color explicitly.

As a quick test, replace CLKComplicationTemplateGraphicExtraLargeCircularView with CLKComplicationTemplateGraphicRectangularFullView in the preview. Notice how the colors are reversed in the full rectangular face. Remember that watchOS decides whether to tint the foreground or the background, depending on the family.

Before continuing through the chapter, please switch back to the CLKComplicationTemplateGraphicExtraLargeCircularView.

Rendering modes

There will be times when you still want a bit more control, depending on whether the user tints the watch face. SwiftUI has you covered!

Add the following property to the top of your view:

@Environment(\.complicationRenderingMode) var renderingMode

watchOS will set that property to .fullColor if the watch face displayed is multicolor or .tinted if the user has a tinted face. Replace the body with:

ZStack {
  // 1
  if renderingMode == .fullColor {
    // 2
    Image("Full")
      .resizable()
      .aspectRatio(contentMode: .fit)
      .complicationForeground()
  } else {
    // 3
    Circle()

    Image("eyesAndMouth")
      .resizable()
      .aspectRatio(contentMode: .fit)
      .complicationForeground()
  }
}

In the preceding code:

  1. Inside of the Stack, you check to determine if the user wants a full-color image.
  2. If they do, then you show the normal full-color image you created.
  3. If the user wants tinting, you show the tinted versions.

Now, when you look at the Canvas, the first image is still the full-color version. By checking the rendering mode, you now have greater control.

You may want completely different images, depending on whether the complication is full-color or not. To see this in action, add the following code right after the Circle() line:

.fill(LinearGradient(
  gradient: Gradient(
    colors: [.red.opacity(0.3), .blue.opacity(1.0)]
  ),
  startPoint: .top,
  endPoint: .bottom))

You filled the circle with a linear gradient, from top to bottom. That’s standard SwiftUI code. However, notice the specified colors. The first is red, and the second is blue. I purposely chose colors so that you could see the actual color is ignored. When using a tinted complication, remember that watchOS only uses the opacity.

It makes for a bit of a creepy smiley face:

Update the complication controller

So far, you’ve performed all of your previewing of the complication from Xcode’s Canvas. Remember, to let the user pick your updated complication you must import SwiftUI in ComplicationController.swift:

import SwiftUI

And then modify currentTimelineEntry(for:) to use the SwiftUI version:

let template = CLKComplicationTemplateGraphicExtraLargeCircularView(
  HappyComplication()
)

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

Now build and run. You’ll see the creepy smile with the gradient you just added.

Key points

  • Be sure to look at your complications on tinted watch faces.
  • Split your Image or Shape items into foreground and background layers.
  • When using tinted complications, watchOS ignores the color and only honors the opacity.

Where to go from here?

In the following chapter, you’ll continue to use complications. But this time, you will focus on SwiftUI complications to round up your knowledge on this useful and sometimes underestimated Apple Watch feature. If you want to get a hint of what to expect:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.