Zum Hauptinhalt springen

So erstellst du eine Apple Watch App für eine Capacitor App

Erstelle eine Apple Watch App für deine Capacitor App: Füge ein watchOS-Target hinzu, schreibe den SwiftUI-Teil und synchronisiere Daten via @capgo/capacitor-watch. mit dem iPhone

Capgo-Banner für einen Leitfaden zum Erstellen einer Apple-Watch-Begleit-App für eine Capacitor-iOS-App

Um eine Apple-Watch-App für eine Capacitor-App zu erstellen, fügst du ein natives watchOS-Target zu deinem iOS-Projekt hinzu, entwickelst die UI in SwiftUI und verbindest sie mit deinem Web-code über das @capgo/capacitor-watch Plugin, das Apples WatchConnectivity-Framework kapselt. Die Handy-App behält ihre Capacitor-Web-Schicht bei, während die Watch-App eine kleine native Begleit-App wird, die Wörterbücher sendet und empfängt.

Dieser Leitfaden zeigt den kompletten Weg mit Capacitor 8 und Xcode 26: die Architektur, das Xcode-Target, den Swift- und TypeScript-code auf beiden Seiten, welche Übertragungsmethode du verwendest, Tests auf Simulatoren und Geräten und wie die Watch-App ausgeliefert wird.

Warum die Watch-UI nativ ist und das Phone Capacitor bleibt

watchOS hat keinen WebView. Es gibt keinen WKWebView auf der Watch, daher kann eine Capacitor-App dort nicht laufen, und kein Plugin ändert etwas daran. Was du tun kannst, ist die Verantwortlichkeiten aufzuteilen:

Schicht Läuft auf Sprache Aufgabe
Capacitor App iPhone TypeScript, dein Framework der Wahl Business-Logik, Netzwerk, Auth, Storage
@capgo/capacitor-watch iPhone Swift (Plugin), JS API Verbindet WatchConnectivity mit JavaScript
CapgoWatchSDK Apple Watch Swift Session-Handling auf der Watch
Watch-App Apple Watch SwiftUI Kleine Displays, auf einen Blick, schnelle Aktionen

In der Praxis ist die Watch-App „abhängig“: Sie erhält ihre Daten vom Telefon. Halte die Logik auf dem Telefon, wo sie bereits in TypeScript existiert, und sende der Watch nur das, was sie zur Anzeige benötigt.

Voraussetzungen

  • Eine Capacitor 8-App mit der iOS-Plattform. Falls du noch eine ältere Version nutzt, folge Capacitor 8 Upgrade-Guide zuerst.
  • Xcode 26 auf einem Mac. Apple verlangt seit April 2026 Xcode 26 für App-Store-Uploads, siehe die Xcode-26-Anforderung.
  • Ein Apple-Developer-Konto für Gerätetests und Distribution.
  • iOS 15 oder neuer auf dem Telefon (Minimum des Plugins) und watchOS 9 oder neuer auf der Watch (Minimum von SDK).
  • Idealerweise ein echtes iPhone, gekoppelt mit einer echten Apple Watch. Simulatoren helfen, decken aber nicht jeden Übertragungstyp ab.

Schritt 1: Plugin auf der Smartphone-Seite installieren

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

Das Plugin aktiviert WCSession sich sofort beim Laden, es gibt keinen init-Aufruf. Beginne damit, den Verbindungsstatus zu lesen:

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

Die vollständige API-Referenz findest du in der Plugin-Docs und Plugin-Seite.

Schritt 2: watchOS-Target in Xcode hinzufügen

Öffne das iOS-Projekt mit bunx cap open ios. Capacitor 8-Projekte, die mit Swift Package Manager erstellt wurden, öffnen App.xcodeproj; ältere CocoaPods-Projekte öffnen App.xcworkspace. Beides funktioniert.

  1. Wähle Datei > Neu > Ziel, wählen Sie watchOS Tab, dann App hinzu.
  2. Gib einen Namen ein, z. B. MyWatch, und verwende SwiftUI als Interface.
  3. Stelle im Options-Sheet sicher, dass die Watch-App als Companion in dein bestehendes App Target eingebettet wird. Diese Einstellung sorgt dafür, dass Xcode die Watch-App in den iPhone-Build einbettet und den Companion Bundle Identifier setzt.
  4. Lass Notification Scenes und Complications deaktiviert, sofern du sie nicht sofort brauchst. Widgets kannst du später hinzufügen.
  5. Bestätige den Prompt, um das neue Scheme zu aktivieren.

Die Bundle-Identifier-Regel

Die Bundle-ID der Watch-App muss mit der Bundle-ID der iPhone-App beginnen. Wenn deine App com.example.shopist, schlägt Xcode com.example.shop.watchkitappvor. Behalte dieses Präfix bei. Wenn du die iOS-Bundle-ID später änderst, ändere auch die der Watch-App, sonst schlägt die Build-Validierung fehl und WatchConnectivity kann die beiden Apps nicht koppeln.

Unter Signing & Capabilities, wähle für beide Targets dasselbe Team. Bei automatischer Signierung registriert Xcode die zweite App-ID und das Provisioning-Profil für dich.

CapgoWatchSDK zum Watch-Target hinzufügen

  1. Datei > Paketabhängigkeiten hinzufügen.
  2. Eingabe https://github.com/Cap-go/capacitor-watch.git.
  3. Verwenden Sie „Bis zur nächsten Major-Version“ ab 8.0.0.
  4. Wenn Xcode fragt, welche Produkte hinzugefügt werden sollen, wählen Sie nur CapgoWatchSDK aus und weisen Sie es dem Watch- Target zu, nicht dem iOS-App-Target. Die iOS-Seite erhält das Plugin bereits über cap sync.

Schritt 3: Die Watch-App in SwiftUI schreiben

Das SDK stellt WatchConnector.shared, ein ObservableObject mit veröffentlichten isReachable, isActivated, lastMessage, und applicationContext Properties bereit. Aktivieren Sie es beim App-Start:

import SwiftUI
import CapgoWatchSDK

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

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

Ein kleines Display, das vom Telefon übermittelte Daten anzeigt und eine Aktion zurücksendet:

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

Der async Variante von sendMessage sendet mit einem Reply-Handler, sodass das Telefon es als messageReceivedWithReply Event erhält. Die callback-basierte Variante sendMessage(_:replyHandler:errorHandler:) ermöglicht es, auf die Antwort zu verzichten.

Wenn Sie Delegate-Callbacks dem Beobachten veröffentlichter Eigenschaften vorziehen, implementieren Sie WatchConnectorDelegate und setze WatchConnector.shared.delegate. Es bietet Methoden für Nachrichten, Nachrichten mit Reply-Handler, Anwendungskontext, Benutzerinformationen, Erreichbarkeit und Aktivierung.

Schritt 4: Kommunikation mit der Watch aus TypeScript

Registrieren Sie Listener frühzeitig, z. B. im App-Bootstrap, damit Events, die beim Start eintreffen, nicht verloren gehen.

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() },
  });
}

Antworten Sie immer auf messageReceivedWithReply. Die Watch wartet, und eine unbeantwortete Anfrage führt zu einem Timeout-Fehler auf der Watch-Seite.

Welche Übertragungsmethode solltest du verwenden?

WatchConnectivity bietet drei Kanäle, und das Plugin mappt jeden davon:

Methode Auslieferung Watch muss erreichbar sein Ideal für
sendMessage Sofort Ja, sonst wird abgelehnt Button-Taps, Live Updates, solange beide Apps offen sind
updateApplicationContext Neuester Wert gewinnt, Zustellung sobald möglich Nein Aktueller Status: Zähler, Einstellungen, eingeloggter User
transferUserInfo In Warteschlange, Zustellung in Reihenfolge Nein Events, die sicher ankommen müssen: geloggte Workouts, erstellte Datensätze

Ein bewährtes Muster: Nutze den Application Context als Source of Truth dafür, was die Watch anzeigt, nutze sendMessage nur für Interaktionen, während die Watch-App offen ist, und nutze transferUserInfo für alles, was nicht verloren gehen darf.

Design-Einschränkungen

  • Payload-Größe. WatchConnectivity lehnt zu große Payloads mit payloadTooLargeab, und Apple gibt keine feste Byte-Anzahl pro Channel an. Halte Payloads klein: IDs, Anzahlen, kurze Strings. Hol dir große Inhalte stattdessen aus deinem API.
  • Payload-Typen. Daten müssen Property-List-kompatibel sein: Strings, Zahlen, Booleans, Datumswerte, Data, Arrays und Dictionaries. null Werte aus JavaScript sind ungültig. Entferne sie vor dem Senden.
  • Die App muss auf dem iPhone laufen, um Events zu verarbeiten. Listener laufen in der Capacitor JavaScript-Runtime. Wenn iOS die App beendet hat, erreichen Events dein TypeScript erst, wenn die App wieder läuft. Kontext und Nutzerinfos bleiben vom System in der Warteschlange; gestalte die Watch-UI aber so, dass sie mit gecachtem Kontext funktioniert.
  • Pairing. Eine Apple Watch lässt sich nur mit einem iPhone koppeln. Android-Nutzer können sie nicht nutzen; die Android-Implementierung des Plugins zielt stattdessen auf Wear OS ab – mehr dazu in unserem Wear OS Guide.
  • Unabhängigkeit. Eine Companion-App auf WatchConnectivity-Basis ist vom iPhone abhängig. Wenn die Watch weit weg vom iPhone funktionieren muss, füge eine zweite Datenquelle hinzu, etwa dein eigenes API über die Netzwerkverbindung der Watch.

Testen auf Simulatoren und Geräten

Du kannst am SwiftUI-Layout im Watch-Simulator iterieren, aber für den Datentransfer brauchst du ein gekoppeltes Setup.

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

Starte die iOS-App auf dem gekoppelten iPhone-Simulator und führe anschließend das Watch-Scheme auf dem Watch-Simulator aus. Beide Apps müssen vorab installiert sein. isWatchAppInstalled true wird. Apple weist darauf hin, dass der Simulator transferUserInfo, also diesen Pfad auf echtem Hardware-Setup prüfen.

Schließe für Geräte das iPhone an, stelle sicher, dass auf der Watch der Developer Mode aktiviert ist, und wähle die Watch als Run Destination für das Watch-Scheme aus. Nutze getInfo() als Smoke Test:

getInfo() Ergebnis Bedeutung
isPaired: false Keine Watch mit diesem Telefon gekoppelt
isWatchAppInstalled: false Watch-App fehlt oder Bundle-ID / Companion-Einstellung falsch
isReachable: false, Rest true Watch-App nicht im Vordergrund oder außerhalb des Bereichs
activationState: 2, alle true Bereit für sendMessage

Fehlerbehebung

isWatchAppInstalled bleibt false. Prüfe das Präfix der Watch-Bundle-ID und stelle sicher, dass das Watch-Target eingebettet ist App Target eingebettet ist (Build Phases > Embed Watch Content), und installiere beide Apps neu.

Nachrichten von der Watch kommen nie in JavaScript an. Registriere Listener, bevor die Watch sendet, und stelle sicher, dass die Watch WatchConnector.shared.activate() beim Start aufruft. Prüfe auf dem Telefon in der Xcode-Konsole den Aktivierungs-Log des Plugins.

sendMessage wird mit „Watch is not reachable“ abgelehnt. Erwartet, wenn die Watch-App geschlossen ist. Fallback auf updateApplicationContext.

replyToMessage scheint ignoriert zu werden. Übergib genau das callbackId aus dem Event und antworte schnell. Der Reply-Handler der Watch hat ein Timeout.

Build schlägt fehl mit „Embedded binary’s bundle identifier is not prefixed“. Die Watch-Bundle-ID muss mit der iOS-Bundle-ID beginnen.

Archive-Upload abgelehnt wegen fehlender Icons. Watch-Targets benötigen ihr eigenes App-Icon in Assets.xcassets.

Veröffentlichung der Watch-App

Die Watch-App ist kein separates Produkt. Sie ist im iOS-Archiv eingebettet, wird zusammen hochgeladen und erscheint im selben App Store Connect-Eintrag, wo du unter dem Watch-Tab Apple Watch-Screenshots hinzufügst. Egal, was dein Archiv erzeugt – lokales Xcode, CI oder Capgo Build, benötigt ein Provisioning-Profil für beide Bundle-IDs und muss Xcode 26 verwenden.

Beachte bei Updates Folgendes. Capgo Live Updates kann Änderungen am Web-Bundle des Telefons ohne Store-Review veröffentlichen, was praktisch ist, wenn die TypeScript-Seite deiner Watch-Bridge einen Bug hat. Swift code auf der Watch und jede Änderung am nativen Plugin gehen weiterhin über den App Store. Versioniere dein Nachrichtenformat, zum Beispiel mit einem v Feld in jedem Dictionary, damit ein neueres Phone-Bundle mit einem älteren Watch-Build zusammenarbeitet.

Fazit

Die Aufteilung ist einfach, wenn man sie akzeptiert: SwiftUI für das Handgelenk, Capacitor für alles andere und drei WatchConnectivity-Kanäle dazwischen. Installieren @capgo/capacitor-watch, füge das Watch-Target mit CapgoWatchSDK hinzu, halte die Payloads klein und teste auf gepaarter Hardware, bevor du einreichst.

Live-Updates für Capacitor-Apps

Wenn ein Bug im Web-Layer live ist, liefere den Fix über Capgo aus, statt Tage auf die App-Store-Freigabe zu warten. Nutzer bekommen das Update im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neues aus unserem Blog

Capgo gibt dir die besten Insights, um eine wirklich professionelle Mobile App zu erstellen.