跳转至

10.Face Sharing

Written by Scott Grosch

While the ability to create and use complications has existed for years, being able to share the perfect watch face with your friends and customers is a newer feature. Most apps you develop will only have a single complication. Sometimes, however, you might provide multiple complications.

Consider the TideWatch app you built in previous chapters. When it makes sense to group multiple complications from your app or other apps, you can now offer a way for your customers to receive a preconfigured watch face easily.

Note

You’ll need a physical device to send a watch face from the iPhone to the Apple Watch.

Sharing Faces

There are four ways you can share a watch face:

  1. Using the Watch app on your iOS device.
  2. Directly from your watch.
  3. Downloading a watchface file from the web.
  4. Adding it via your custom iOS app.

From the iPhone

One of the simplest ways to share a watch face is by running the Watch app on your iPhone. When the app opens, you’ll see all of the watch faces you have currently configured:

img

Long-press any of the watch faces. You’ll see a sheet appear which will let you share the face:

img

From the Apple Watch

It’s even easier to share from the Apple Watch. From the home screen, long-press the watch face and choose the share icon. Then select to whom you’d like to send the watch face:

img

From the Web

If you want to share multiple watch faces, you may wish to host them on your app’s website. Point your favorite browser to Apple’s Human Interface Guide (HIG). Scroll down to the Technologies section, and you’ll see a download available for Add Apple Watch Face. Download the file, which is a DMG, and then double-click it. You’ll see a window containing the developer license as well as an Assets folder.

The Assets folder contains multiple images you can place on your webpage to let customers know they can download a watch face. Each style of button contains images that work well with both light and dark mode websites. Apple has also provided the images in PDF, PNGand SVG formats.

Once you create a watch face on your physical device that looks the way you want with the appropriately placed complications, share the face with yourself by choosing the Mail app. Unlike the other sharing types, if you choose Mail, you’ll get both the watch face file and a graphic showing how the face looks.

Note

The preview is only included in the email if you share the face from the Watch app on the iPhone, not directly from the Apple Watch.

Now, you simply create a standard webpage that showcases your awesome watch face with a few simple steps:

  1. Choose the SVG graphic you wish to use for the download button.
  2. Save both files that you mailed to yourself when sharing the face.
  3. Create a page that displays the watch face and has accompanying download buttons.

You can quickly create a page like this:

img

Using your favorite text editor, create a file called index.html somewhere on your Mac. It shouldn’t be part of the Xcode project.

<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>My Cool App</h1>
<p>We really think you'll love this watch face!</p>

<hr />
<div>
  <img src="preview.png">
  <br/>
  <br/>

  <a href="FaceToShare.watchface"
     type="application/vnd.apple.watchface">
    <img src="download.svg">
  </a>
</div>

<div>
  <p>
    You can scan the below QR code to install the watch face from your
    paired iOS device.
  </p>
  <img alt="QR Code" class="qrcode" src="qrcode.png">
</div>
</body>
</html>

After saving the file, double-click on it in Finder to launch your web browser.

Obviously, that HTML shows how quickly you can make your watch faces available to your customers. A production website would, of course, look much nicer and include all appropriate HTML tags.

Notice, on the anchor tag, the <a> element, the referenced location is the watchface file. Then inside the anchor, the image points to the SVG graphic you chose from the HIG. That’s one example of making a clickable “button” to download the watch face. Also, notice that the anchor tag’s type attribute is application/vnd.apple.watchface.

While the download button is great, you can’t assume that your customer has arrived on your website while browsing on their phone. If they’re surfing the web on their laptop, and decide they want your watch face, then providing a QR code that points to the downloadable watch face makes their life easier. They just pull out their phone, point at the screen and download your watch face.

Take a look at Watchfacely for great examples of webpages providing shared watch faces. Health management is a specific example showing a shared health management face.

From the iOS App

You have one final option for providing a preconfigured watch face. You can embed the face right into your iOS app. The advantage of this approach is that the person using your app doesn’t have to transition to a website. You simply provide them some mechanism to send the face directly to the paired Apple Watch via one of the HIG buttons you downloaded.

Open FaceSharing.xcodeproj from this chapter’s starter materials. You’ll notice the project already includes a few items for you:

  • The FaceToShare.watchface file.
  • The appropriate HIG button to share the face is in Assets.xcassets.
  • The face’s preview image is in Assets.xcassets.
  • Session with the standard boilerplate.

Note

Be sure you include any .watchface file you add to Xcode in the FaceSharingtarget membership. If you use Xcode to add the file, that’s automatic. If you drag the file in from Finder, then it isn’t.

Unlike other chapters in this book, there’s no watchOS target. iOS handles sending a watch face and only requires an active and paired Apple Watch.

Setting Up the Session

In Chapter 4, “Watch Connectivity”, you learned about WCSession and WCSessionDelegate. To know whether an Apple Watch is paired to the iOS device, you’ll need the same type of session running. If there’s not a paired Apple Watch, then you wouldn’t want to provide the option to install a custom watch face.

Open Session. Add a property to Session to track whether or not you should show a share button, as well as the method which will update the value:

// 1
@MainActor
@Published
var showFaceSharing = false

private func updateFaceSharing(_ session: WCSession) {
  // 2
  let activated = session.activationState == .activated
  let paired = session.isPaired

  // 3
  Task { @MainActor in
    self.showFaceSharing = activated && paired
  }
}

In the preceding code:

  1. You use @Published to notify SwiftUI when changes to the property occur. Since the property intends to determine if you should display a UI element, you’ll also want to use @MainActor so that Xcode helps ensure you only update the property from the main thread.
  2. Using the supplied WCSession, you determine whether the session is active on a paired Apple Watch. No need to check if the app is installed.
  3. Finally, you set showFaceSharing, ensuring you first dispatch to the main queue since you’re triggering UI updates.

Next, in sessionDidBecomeInactive(_:) and session(_:activationDidCompleteWith:error:), you call the method you just wrote. So add this line to both methods:

updateFaceSharing(session)

Add one more delegate method:

func sessionWatchStateDidChange(_ session: WCSession) {
  updateFaceSharing(session)
}

This way, you’ve ensured showFaceSharing always knows whether or not there’s an active and paired Apple Watch.

Showing the Share Button

Now open ContentView. Currently, you only show an image of the face which you’ll send to the watch. You need to include a download button, but only if there’s a paired Apple Watch.

Start by adding the following code, right after the Image line:

// 1
if session.showFaceSharing {
} else {
  // 2
  Text("Unable to share watch face")
    .font(.title)
  // 3
  Text("Please pair an Apple Watch first")
    .font(.title3)
}

Here’s a code breakdown:

  1. By looking at the published property you just configured in Session, SwiftUI can determine what to display.
  2. If face sharing isn’t available, you tell the user they can’t share the face in a .title font.
  3. It’s also a good idea to identify why they can’t share the face by telling them there’s no paired Apple Watch in a slightly smaller .title3 font.

Remember that SwiftUI reevaluates the body whenever a property marked as @Published changes. If face sharing is available, you need to call a method that transfers the face.

Add the following method:

// 1
func sendWatchFace() async {
  // 2
  guard let url = Bundle.main.url(
    forResource: "FaceToShare",
    withExtension: "watchface"
  ) else {
    // 3
    fatalError("You didn't include the watchface file in the bundle.")
  }
}

Here’s what’s happening in that code:

  1. You defined a method to transfer the face. Because it might take a bit of time, you mark the method as async.
  2. You load the watchface file that you included in the iOS target.
  3. While a fatalError call is usually bad, it’s appropriate in this situation as you want to catch the missing bundle file during development rather than once you’ve published the app.

The library you’ll use to actually perform the transfer comes from ClockKit. So, add the appropriate import to the top of the file:

import ClockKit

Finally, you’ll use the CLKWatchFaceLibrary to perform the transfer. Add the following to the end of sendWatchFace:

// 1
let library = CLKWatchFaceLibrary()

// 2
do {
  // 3
  try await library.addWatchFace(at: url)
} catch {
  // 4
  print(error.localizedDescription)
}

In the preceding code:

  1. As mentioned, you create an object of type CLKWatchFaceLibrary.
  2. The transfer might fail, so you need to wrap the call in a do/catch block.
  3. You try to perform addWatchFace(at:) by passing the URL of the face you loaded from the Bundle. As the method is asynchronous, you add the await keyword after try.
  4. If the face can’t be transferred, you’ll need to handle the error. In a production app, that would likely mean displaying some type of alert.

Note

Remember, it’s always try await, never await try. You try to wait for the call to complete. You don’t wait for a method to try and run.

According to Apple’s documentation on addWatchFace(at:):

All of the complications on the watch face must come from apps with a valid App Store ID, such as an app from the App Store or a TestFlight build. If you try to use complications from a development build, the system won’t recognize the development ID as a valid App Store ID.

Now it’s time to use your new method. Go back to the body definition and inside of the if condition, add:

Button {
  Task { await sendWatchFace() }
} label: {
  Image("ShareButton")
}

That’s pretty standard SwiftUI for adding a button with one exception. When the user clicks the button, you create a new top-level Task. The method sendWatchFace is asynchronous, but the button click’s action isn’t aware of the asynchronous method.

By wrapping the call in the Task block, you allow the method call to still properly run asynchronously. This is a common pattern you’ll use when calling async methods from SwiftUI bodies.

At this point, you’re ready to test things out. Build and run the app in the Simulator, not on your physical device. You’ll see the message saying you can’t share the watch face. The Simulator isn’t configured with a paired watch face.

img

Build and run the app again, this time targeting your iPhone. This time you can share the watch face. Of course, you’ll have to have a paired Apple Watch handy! :]

img

Tap the add button, and you’ll be asked to add the face to your Apple Watch:

img

While it seems redundant since you just tapped the download button, Apple wants to make sure faces don’t magically appear on your devices without you being aware.

Note

Never install a face to the watch without the user explicitly requesting it via a download button.

After you tap Add to My Faces, go to the Watch app on your iPhone, and the new watch face should now be visible:

img

Finally, for the pièce de résistance. Set the newly installed watch face and then glance at your Apple Watch. You’ll see your shiny new watch face!

img

Key Points

  • Only install a watch face if the user requests it.
  • Don’t offer to install a watch face if there is no paired device.
  • Anyone can create shareable watch faces, but only you can offer them from inside the app.

Where to Go From Here?

  • The sample materials for this chapter contain a web folder that includes the full source to the sample webpage.
  • Check out the amazing Watchfacely site for great examples of sharing faces via the web.
  • Many sites on the web will generate QR codes for free, such as QR Code Generator.