CapacitorLABS - This project is experimental. Support is not provided. Please open issues when needed.
The Capacitor Watch plugin allows you to define a UI for a watch in your web code and show it on a paired watch.
This currently only supports iOS. This guide assumes you've already added iOS to your capcacitor project.
Also note - all of this will only work with an actual Apple Watch. Simulators don't allow the app<->watch communcation like real devices do.
Step 1
Add the watch plugin to your capacitor project, and then open the Xcode project:
npm install @capacitor/watch
npx cap sync
npx cap open ios
Step 2
Go to add capabilities:
Add the 'Background Modes' and 'Push Notification' capabilities. Then in the Background Modes options, select 'Background Fetch', 'Remote Notifications', and 'Background Processing'. Your App target should look like this:
Step 3
Open AppDelegate.swift
and add import WatchConnectivity
and import CapactiorWatch
to the top of the file, and the following code inside the application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
method:
assert(WCSession.isSupported(), "This sample requires Watch Connectivity support!")
WCSession.default.delegate = CapWatchSessionDelegate.shared
WCSession.default.activate()
Step 4
Select File -> New -> Target in Xcode, and then the watchOS tab, and 'App':
Click 'Next' then fill out the options like so:
This dialog can be a little confusing, the key thing is your 'Bundle Identifier' must be [your apps bundle ID].watchapp
for the watch<->app pairing to work. You must also pick SwiftUI for the Interface and Swift for the language. The project should be App
.
Step 5
We're going to add the code that makes Capacitor Watch work in the watch application.
If you are using Xcode 15 or beyond you then need to add the Capacitor Watch Swift Package from your node_modules:
First go to the project package dependancies
Then choose 'Add Local'
Then navigate into the node_modules/@capacitor/watch/CapWatch-Watch-SPM
folder and click 'Add Package'
Then in the column on the right pick your watch app to be the target and click 'Add Package'
Once this is done your Package Dependancies should look like this:
With Xcode 14 you will need to go here https://github.com/ionic-team/CapacitorWatch/tree/main/packages/iOS-capWatch-watch/Sources/iOS-capWatch-watch and copy all the files into your Watch project and make sure the target selected is your watch app. It should look like so:
Step 6
Then open the watch app's 'Main' file which should be watchappApp.swift
. Add the lines import WatchConnectivity
and import iOS_capWatch_watch
above the @main
statement. Then replace the line that says ContentView()
with this:
The finished file should look like this:
import SwiftUI
import WatchConnectivity
import iOS_capWatch_watch
@main
struct watchddgg_Watch_AppApp: App {
var body: some Scene {
WindowGroup {
CapWatchContentView()
.onAppear {
assert(WCSession.isSupported(), "This sample requires Watch Connectivity support!")
WCSession.default.delegate = WatchViewModel.shared
WCSession.default.activate()
}
}
}
}
Step 7
Add the 'Background Modes' capability to the watch app target, and enable 'Remote Notifications':
You should be ready to develop for Capcacitor Watch now!
You can still develop your iOS app like a normal capacitor app, but getting things to run on the watch requires you to change the target and destination in Xcode. You can change this with the 'Target Dropdown' near the center-top of Xcode:
The right half of this bar lets you pick the destination device or simulator. You will need to pick the watch paired with the phone and then hit the 'Run' button or use the 'cmd+r' run shortcut.
There can be some challenges in syncing the watch and phone apps. Sometimes you will get an error in the xcode console complaining the compainion app is not present. The best solution in this case is to re-build and re-install the apps on both devices.
You will use a long string to define the watch UI. A newline delimits components. Currently this plugin only supports a vertical scroll view of either Text or Button components.
Once you've defined your UI you can send it to the watch using the updateWatchUI()
method:
async uploadMyWatchUI() {
const watchUI =
`Text("Capacitor WATCH")
Button("Add One", "inc")`;
await Watch.updateWatchUI({"watchUI": watchUI});
}
Will produce this:
This article provides a great summary on the native methods and their implications: https://alexanderweiss.dev/blog/2023-01-18-three-ways-to-communicate-via-watchconnectivity
On the phone side, you can implement these methods using the Capacitor Background Runner Plugin (https://github.com/ionic-team/capacitor-background-runner). Currently the watch plugin will mainly handle the didReceiveUserInfo
method, and you can recieve envents from the watch while your app is in the background using the following code in your runner.js:
addEventListener("WatchConnectivity_didReceiveUserInfo", (args) => {
console.log(args.message.jsCommand);
})
You can also implment the runCommand
event listener for foreground procesing:
Watch.addListener("runCommand", (data: {command: string}) => {
console.log("PHONE got command - " + data.command);
})
The commands are the 2nd paramter in the Button()
definition of the watch UI. This can be any string.
You can add variables to Text()
elements by using a $
variable and updating with the updateWatchData
command:
Text("Show my $number")
This example will update $number
when executed:
var stateData = {
number: 0
}
async function counterIncrement() {
stateData.counter++
await Watch.updateWatchData({"data": convertValuesOfObjectToStringValues(stateData)})
}
Capacitor Watch will persist the last UI you sent with updateWatchUI()
. State from updateWatchData()
is NOT preserved.
npm install @capacitor/watch
npx cap sync
addListener(eventName: 'runCommand', listenerFunc: (data: { command: string; }) => void) => Promise<PluginListenerHandle> & PluginListenerHandle
Listen for a command from the watch
Param | Type |
---|---|
eventName |
'runCommand' |
listenerFunc |
(data: { command: string; }) => void |
Returns: Promise<PluginListenerHandle> & PluginListenerHandle
updateWatchUI(options: { watchUI: string; }) => Promise<void>
Replaces the current watch UI with watchUI
Param | Type |
---|---|
options |
{ watchUI: string; } |
updateWatchData(options: { data: { [key: string]: string; }; }) => Promise<void>
Updates the watch's state data
Param | Type |
---|---|
options |
{ data: { [key: string]: string; }; } |
Prop | Type |
---|---|
remove |
() => Promise<void> |