Skip to content

ionic-team/CapacitorWatch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

91 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@capacitor/watch


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.

Install

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!

Development workflow

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.

Building the watch UI and sending it to the watch

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:

Communicating with the watch

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.

Updating watch data

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)})
}

Persistance on the Watch

Capacitor Watch will persist the last UI you sent with updateWatchUI(). State from updateWatchData() is NOT preserved.