メインコンテンツへスキップ

Capacitor アプリ向け Apple Watch アプリのビルド方法

Capacitor アプリ用の Apple Watch アプリを開発: watchOS ターゲットを追加し、SwiftUI 側を実装、@capgo/capacitor-watch. で iPhone とデータを同期

Capacitor iOSアプリ向けのApple Watchコンパニオンアプリ開発ガイド Capgo バナー

To build an Apple Watch app for a Capacitor app, you add a native watchOS target to your iOS project, write its UI in SwiftUI, and connect it to your web code with the @capgo/capacitor-watch plugin, which wraps Apple’s WatchConnectivity framework. The phone app keeps its Capacitor web layer, and the watch app becomes a small native companion that sends and receives dictionaries.

このガイドでは、Capacitor 8 と Xcode 26 を使用した全工程を解説します。アーキテクチャ、Xcode ターゲット、各サイドの Swift および TypeScript code、最適な転送方法、シミュレーターと実機でのテスト、そして watch アプリの配信方法まで網羅しています。

なぜ watch の UI はネイティブで、iPhone は Capacitor のままなのか

watchOS には WebView が搭載されていません。そのため、watch 上には WKWebView 存在せず、Capacitor アプリを動作させることはできません。プラグインでもこの制限は回避できません。解決策は、役割を分担させることです:

レイヤー 実行環境 言語 役割
Capacitor アプリが iPhone TypeScript、お好みのフレームワーク ビジネスロジック、ネットワーク、認証、ストレージ
@capgo/capacitor-watch iPhone Swift (プラグイン), JS API WatchConnectivity を JavaScript にブリッジ
CapgoWatchSDK Apple Watch Swift Watch 上でのセッション管理
Watch アプリ Apple Watch SwiftUI 小画面、一目でわかる UI、クイックアクション

実際には、Watch アプリは「依存型」です。データは iPhone から取得します。ロジックは、すでに TypeScript で書かれている iPhone 側に置いたままにし、Watch には表示に必要なものだけを送りましょう。

前提条件

  • iOSプラットフォームを追加した Capacitor 8アプリ。古いバージョンを使用している場合は、 Capacitor 8 アップグレードガイド してください。
  • Mac上のXcode 26を使用してください。Appleは2026年4月よりApp StoreへのアップロードにXcode 26を必須としています。詳細は Xcode 26の要件を確認してください.
  • デバイスでのテストおよび配布用のApple Developerアカウント。
  • iPhone側はiOS 15以降(プラグインの最小要件)、Apple Watch側はwatchOS 9以降(SDK の最小要件)が必要です。
  • 理想的には、実機のiPhoneとApple Watchをペアリングして使用してください。シミュレーターも役立ちますが、すべての転送タイプを網羅しているわけではありません。

ステップ 1: iPhone側にプラグインをインストールする

bun add @capgo/capacitor-watch
bunx cap sync ios

プラグインはロードされると WCSession 即座にアクティブ化されるため、init呼び出しは不要です。まずは接続状態の読み取りから始めましょう:

import { CapgoWatch } from '@capgo/capacitor-watch';

const info = await CapgoWatch.getInfo();
console.log({
  supported: info.isSupported,          // false on iPad, Android without Wear OS, and web
  paired: info.isPaired,                // an Apple Watch is paired with this iPhone
  installed: info.isWatchAppInstalled,  // your watch app is on that watch
  reachable: info.isReachable,          // live messaging is possible right now
  state: info.activationState,          // 0 notActivated, 1 inactive, 2 activated
});

API の完全なリファレンスはこちら: プラグインのドキュメント および プラグインのページ.

ステップ 2: Xcode で watchOS ターゲットを追加する

を使用して iOS プロジェクトを開きます: bunx cap open ios. Swift Package Manager で作成された Capacitor 8 プロジェクトを開く場合 App.xcodeproj; 古い CocoaPods プロジェクトを開く場合 App.xcworkspace. どちらでも可能です。

  1. 選択: File > New > Targetから、以下を選択してください: watchOS タブを選択し、次に アプリ.
  2. 名前を入力します(例: MyWatch)、そして SwiftUI をインターフェースとして使用します。
  3. オプションシートで、watchアプリが既存の App targetのコンパニオンとして埋め込まれていることを確認してください。この設定により、XcodeはiPhoneのビルドにwatchアプリを組み込み、コンパニオンのbundle identifierを設定します。
  4. 今すぐ必要でない限り、notification scenesとcomplicationsはオフのままにしておいてください。ウィジェットは後から追加可能です。
  5. プロンプトが表示されたら、新しいschemeをアクティベートします。

bundle identifierのルール

WatchアプリのBundle IDは、iPhoneアプリのBundle IDから始まる必要があります。アプリが com.example.shopの場合、Xcodeは次を提案します: com.example.shop.watchkitappこのプレフィックスは維持してください。後でiOSのBundle IDを変更した場合は、Watchアプリ側も変更してください。そうしないと、ビルドのバリデーションに失敗し、WatchConnectivityによる2つのアプリのペアリングができなくなります。

未満 Signing & Capabilitiesの場合、両方のターゲットで同じチームを選択してください。Automatic signingを使用していれば、Xcodeが2つ目のApp IDとプロビジョニングプロファイルを自動的に登録します。

WatchターゲットにCapgoWatchSDKを追加する

  1. File > Add Package Dependencies.
  2. を入力 https://github.com/Cap-go/capacitor-watch.git.
  3. Dependency Ruleは「Up to Next Major Version」を選択 8.0.0.
  4. Xcodeで追加するプロダクトの選択を求められたら、以下のみを選択してください: CapgoWatchSDK そして、それを Apple Watch ターゲットに割り当てます(iOSアプリ側ではありません)。iOS側はすでに次の方法でプラグインを取得しています: cap sync.

ステップ 3: SwiftUI で watch アプリを書く

SDK は次を公開します: WatchConnector.shared、つまり ObservableObject 公開された isReachable, isActivated, lastMessageや applicationContext プロパティを持つものです。アプリ起動時に有効化してください:

import SwiftUI
import CapgoWatchSDK

@main
struct MyWatchApp: App {
    init() {
        WatchConnector.shared.activate()
    }

    var body: some Scene {
        WindowGroup {
            OrdersView()
        }
    }
}

スマホからプッシュされたデータを表示し、アクションを返す小さな画面:

import SwiftUI
import CapgoWatchSDK

struct OrdersView: View {
    @ObservedObject private var connector = WatchConnector.shared
    @State private var status = ""

    var openOrders: Int {
        connector.applicationContext["openOrders"] as? Int ?? 0
    }

    var body: some View {
        VStack(spacing: 8) {
            Text("\(openOrders)")
                .font(.system(size: 44, weight: .bold))
            Text("open orders")
                .font(.footnote)
                .foregroundStyle(.secondary)

            Button("Refresh") {
                refresh()
            }
            .disabled(!connector.isReachable)

            if !status.isEmpty {
                Text(status).font(.caption2)
            }
        }
    }

    private func refresh() {
        Task {
            do {
                let reply = try await connector.sendMessage(["action": "refresh"])
                status = "Updated \(reply["count"] as? Int ?? 0)"
            } catch {
                status = "iPhone not reachable"
            }
        }
    }
}

この async のバリエーション sendMessage reply handler付きで送信されるため、iPhone側では messageReceivedWithReply イベントとして受信します。コールバックベースのバリアントなら sendMessage(_:replyHandler:errorHandler:) replyをスキップできます。

publishedプロパティの監視よりもdelegateコールバックを好む場合は、 WatchConnectorDelegate 設定してください WatchConnector.shared.delegateに準拠してください。メッセージ、reply handler付きメッセージ、アプリケーションコンテキスト、ユーザー情報、reachability、activationのメソッドが用意されています。

ステップ 4: TypeScriptからwatchと通信する

起動時に届くイベントを見逃さないよう、アプリのbootstrap時など早い段階でリスナーを登録してください。

import { CapgoWatch } from '@capgo/capacitor-watch';

export async function setupWatchBridge(getOpenOrders: () => Promise<number>) {
  // Watch asked for something and is waiting for an answer
  await CapgoWatch.addListener('messageReceivedWithReply', async (event) => {
    if (event.message.action === 'refresh') {
      const count = await getOpenOrders();
      await CapgoWatch.replyToMessage({
        callbackId: event.callbackId,
        data: { count },
      });
      await pushStateToWatch(count);
    }
  });

  // Fire-and-forget messages from the watch
  await CapgoWatch.addListener('messageReceived', (event) => {
    console.log('watch says', event.message);
  });

  await CapgoWatch.addListener('reachabilityChanged', (event) => {
    console.log('watch reachable:', event.isReachable);
  });

  await pushStateToWatch(await getOpenOrders());
}

export async function pushStateToWatch(openOrders: number) {
  const info = await CapgoWatch.getInfo();
  if (!info.isSupported || !info.isPaired || !info.isWatchAppInstalled) return;

  // Latest state only, delivered when the watch app next runs
  await CapgoWatch.updateApplicationContext({
    context: { openOrders, updatedAt: Date.now() },
  });
}

必ず messageReceivedWithReplyにreplyを返してください。watch側は待機しており、応答のないリクエストはwatch側でタイムアウトエラーになります。

どの転送方法を使うべきですか?

WatchConnectivityには3つのチャネルがあり、プラグインはそれぞれを以下のようにマッピングします:

メソッド 配信 Watchが接続可能であること 適した用途
sendMessage 即時 はい(それ以外は拒否) ボタンタップ、両アプリ起動中のライブ更新
updateApplicationContext 最新値を優先、可能な限り配信 いいえ 現在の状態: カウント、設定、ログインユーザー
transferUserInfo キューに保存、順序通りに配信 いいえ 必ず届けるべきイベント:記録されたワークアウト、作成されたレコード

推奨されるパターン:Watch側で表示する情報の信頼できるソースとしてアプリのコンテキストを使用し、 sendMessage Watchアプリが開いている間のインタラクションのみに限定して使用し、 transferUserInfo 決して妥協できないものすべてのために。

設計時に考慮すべき制限事項

  • ペイロードのサイズ WatchConnectivityは、サイズが大きすぎるペイロードを payloadTooLargeとして拒否します。Appleは各チャネルの固定バイト数を公開していません。ID、カウント、短い文字列など、ペイロードは最小限に抑えてください。大きなコンテンツは API から取得するようにしましょう。
  • ペイロードの型 データはproperty-list互換である必要があります:string、number、boolean、date、data、array、dictionary。 null JavaScriptからの値はそのままでは無効です。送信前に取り除いてください。
  • イベントを処理するには、iPhone側でアプリが実行されている必要があります。 リスナーは Capacitor JavaScript ランタイム内で動作します。iOS によってアプリが終了されている場合、アプリが再起動するまでイベントは TypeScript に届きません。コンテキストやユーザー情報はシステムによってキューに保持されますが、キャッシュされたコンテキストから動作するように watch UI を設計してください。
  • ペアリング Apple Watch は iPhone とのみペアリング可能です。Android ユーザーは利用できず、このプラグインの Android 実装は代わりに Wear OS を対象としています。詳細は以下の Wear OS ガイド.
  • 独立性 WatchConnectivity で構築されたコンパニオンアプリは、スマートフォンに依存します。ウォッチをスマートフォンの離れた場所で使用する必要がある場合は、ウォッチのネットワーク接続を介して独自の API などのセカンドデータソースを追加してください。

シミュレータと実機でのテスト

watch シミュレータで SwiftUI のレイアウトを繰り返し調整できますが、データ転送にはペアリングされたセットアップが必要です。

xcrun simctl list devices
xcrun simctl pair <watch-udid> <phone-udid>

まずペアリングされた iPhone シミュレータで iOS アプリを実行し、次に watch シミュレータで watch スキームを実行します。両方のアプリがインストールされて初めて isWatchAppInstalled が true になります。Apple は、シミュレータが transferUserInfoをサポートしていないと述べているため、実機でそのパスを確認してください。

実機の場合は、iPhone を接続し、watch のデベロッパーモードが有効であることを確認した上で、watch スキームの実行先として watch を選択してください。 getInfo() スモークテストとして:

getInfo() 結果 意味
isPaired: false この端末にペアリングされたWatchがありません
isWatchAppInstalled: false Watchアプリが未導入、またはBundle ID / Companion設定が誤っています
isReachable: false、それ以外はtrue Watchアプリがフォアグラウンドにないか、通信範囲外です
activationState: 2、すべてtrue 準備完了: sendMessage

トラブルシューティング

isWatchAppInstalled falseのままです。 WatchのBundle IDのプレフィックスを確認し、Watchターゲットが〜に埋め込まれているかを確認してください App ターゲット (Build Phases > Embed Watch Content) を設定し、両方のアプリを再インストールしてください。

WatchからのメッセージがJavaScriptに届きません。 Watchが送信を開始する前にリスナーを登録し、Watchからの呼び出しを確認してください。 WatchConnector.shared.activate() 起動時に確認します。iPhone側では、Xcodeのコンソールでプラグインの起動ログを確認してください。

sendMessage “Watch is not reachable” というエラーで拒否されます。 Watchアプリが閉じている場合に発生します。代わりに、 updateApplicationContext.

replyToMessage 無視されているようです。 正確な callbackId をイベントから取得し、迅速にレスポンスを返してください。Watchのreply handlerにはタイムアウトがあります。

“Embedded binary’s bundle identifier is not prefixed” というエラーでビルドが失敗します。 Watchのbundle IDは、iOSのbundle IDで始まる必要があります。

アイコンが不足しているため、Archiveのuploadが拒否されました。 Watchターゲットには、専用のアプリアイコンが Assets.xcassets.

Watchアプリのリリース

Watchアプリは独立した製品ではありません。iOSのアーカイブに組み込まれ、一緒にアップロードされ、App Store Connectの同じレコードに表示されます。WatchタブからApple Watchのスクリーンショットを追加してください。アーカイブを作成する環境(ローカルのXcode、CI、または Capgo のビルド)には、両方のbundle IDに対するプロビジョニングプロファイルが必要で、Xcode 26を使用する必要があります。

アップデートに関する注意点が1つあります。 Capgo Live Updates を使用すれば、ストア審査なしでスマホ側のweb bundleに変更を適用できます。これはWatchブリッジのTypeScript側にバグがある時に非常に便利です。ただし、Watch上のSwift codeやネイティブプラグインへの変更は、引き続きApp Storeの審査が必要です。メッセージ形式には、例えば v フィールドなどを用いてバージョン管理を行い、新しいスマホ側のbundleが古いWatchのビルドとも互換性を保てるようにしてください。

まとめ

この構成を理解すれば、仕組みはシンプルです。手首にはSwiftUI、それ以外にはCapacitor、そしてその間を繋ぐ3つのWatchConnectivityチャネル。まず @capgo/capacitor-watchをインストールし、CapgoWatchSDKでWatchターゲットを追加してください。ペイロードは小さく保ち、提出前にペアリングされた実機でテストしましょう。

Capacitor アプリ向けの Live Updates

Web レイヤーのバグが本番で発生したら、App Store の審査を何日も待たずに Capgo で修正を配信しましょう。ネイティブの変更は通常の審査フローに残したまま、ユーザーはバックグラウンドでアップデートを受け取れます。

最新のブログ記事

Capgoは、真にプロフェッショナルなモバイルアプリを作るために必要な最高のインサイトを提供します。