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:
- Using the Watch app on your iOS device.
- Directly from your watch.
- Downloading a watchface file from the web.
- 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:
Long-press any of the watch faces. You’ll see a sheet appear which will let you share the face:
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:
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:
- Choose the SVG graphic you wish to use for the download button.
- Save both files that you mailed to yourself when sharing the face.
- Create a page that displays the watch face and has accompanying download buttons.
You can quickly create a page like this:
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:
- 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. - 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. - 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:
- By looking at the published property you just configured in Session, SwiftUI can determine what to display.
- If face sharing isn’t available, you tell the user they can’t share the face in a
.title
font. - 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:
- You defined a method to transfer the face. Because it might take a bit of time, you mark the method as
async
. - You load the watchface file that you included in the iOS target.
- 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:
- As mentioned, you create an object of type
CLKWatchFaceLibrary
. - The transfer might fail, so you need to wrap the call in a
do
/catch
block. - You
try
to performaddWatchFace(at:)
by passing the URL of the face you loaded from theBundle
. As the method is asynchronous, you add theawait
keyword aftertry
. - 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.
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! :]
Tap the add button, and you’ll be asked to add the face to your Apple Watch:
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:
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!
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.