Salta al contenuto principale

Come creare un'app per Apple Watch per un'app Capacitor

Crea un'app Apple Watch per la tua app Capacitor: aggiungi un target watchOS, scrivi la parte SwiftUI e sincronizza i dati con l'iPhone usando @capgo/capacitor-watch.

Banner Capgo per una guida alla creazione di un'app companion per Apple Watch per un'app iOS Capacitor

Per creare un'app Apple Watch per un'app Capacitor, aggiungi un target watchOS nativo al tuo progetto iOS, scrivi la sua UI in SwiftUI e collegala al tuo web code con il @capgo/capacitor-watch plugin, che incapsula il framework WatchConnectivity di Apple. L'app per smartphone mantiene il suo layer web Capacitor, mentre l'app per orologio diventa un piccolo companion nativo che invia e riceve dizionari.

Questa guida copre l'intero percorso su Capacitor 8 e Xcode 26: l'architettura, il target Xcode, il code Swift e TypeScript su entrambi i lati, quale metodo di trasferimento usare, il testing su simulatori e dispositivi, e come viene distribuita l'app per orologio.

Perché l'UI dell'orologio è nativa e lo smartphone resta Capacitor

watchOS non include un WebView. Non c'è WKWebView sull'orologio, quindi un'app Capacitor non può esserci eseguita, e nessun plugin cambia questo. Quello che puoi fare è dividere le responsabilità:

Livello Esegue su Lingua Compito
app Capacitor iPhone TypeScript, il framework che preferisci Logica di business, rete, auth, storage
@capgo/capacitor-watch iPhone Swift (plugin), JS API Punta WatchConnectivity verso JavaScript
CapgoWatchSDK Apple Watch Swift Gestione sessioni sull'orologio
App Watch Apple Watch SwiftUI Schermi piccoli, UI a colpo d'occhio, azioni rapide

In pratica l'app watch è “dipendente”: prende i dati dal telefono. Tieni la logica sul telefono, dove esiste già in TypeScript, e invia all'orologio solo ciò che deve mostrare.

Prerequisiti

  • Un'app Capacitor 8 con la piattaforma iOS aggiunta. Se sei ancora su una versione precedente, segui la guida all'aggiornamento a Capacitor 8 prima.
  • Xcode 26 su un Mac. Apple richiede Xcode 26 per gli upload su App Store da aprile 2026, vedi il requisito Xcode 26.
  • Un account Apple Developer per il test su dispositivo e la distribuzione.
  • iOS 15 o successivo sul telefono (minimo del plugin) e watchOS 9 o successivo sull'orologio (minimo di SDK).
  • Idealmente, un iPhone reale abbinato a un Apple Watch reale. I simulatori aiutano, ma non coprono tutti i tipi di trasferimento.

Passo 1: installa il plugin lato telefono

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

Il plugin si attiva WCSession non appena viene caricato, quindi non serve una chiamata init. Inizia leggendo lo stato della connessione:

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

La documentazione completa di API si trova in docs del plugin e il pagina del plugin.

Passo 2: Aggiungi un target watchOS in Xcode

Apri il progetto iOS con bunx cap open ios. Capacitor 8 progetti creati con Swift Package Manager si aprono App.xcodeproj; i vecchi progetti CocoaPods si aprono App.xcworkspace. Entrambi vanno bene.

  1. Usa File > Nuovo > Target, seleziona il watchOS tab, quindi App.
  2. Dai un nome, ad esempio MyWatch, e usa SwiftUI come interfaccia.
  3. Nel pannello delle opzioni, assicurati che l'app watch sia incorporata nel tuo App target esistente come companion. Questa impostazione fa sì che Xcode incorpori l'app watch nella build per iPhone e imposti il bundle identifier companion.
  4. Disabilita le notifiche e le complicazioni a meno che non ti servano subito. Puoi aggiungere i widget in seguito.
  5. Accetta il prompt per attivare il nuovo scheme.

La regola del bundle identifier

Il bundle ID dell'app watch deve iniziare con quello dell'app iPhone. Se la tua app è com.example.shop, Xcode propone com.example.shop.watchkitapp. Mantieni quel prefisso. Se cambi il bundle ID di iOS in seguito, cambia anche quello del watch, altrimenti la build non supererà la validazione e WatchConnectivity non accoppierà le due app.

Sotto Signing & Capabilities, seleziona lo stesso team per entrambi i target. Con l'automatic signing, Xcode registra per te il secondo App ID e il provisioning profile.

Aggiungi CapgoWatchSDK al target del watch

  1. File > Aggiungi dipendenze del pacchetto.
  2. Inserisci https://github.com/Cap-go/capacitor-watch.git.
  3. Usa “Up to Next Major Version” da 8.0.0.
  4. Quando Xcode chiede quali prodotti aggiungere, seleziona solo CapgoWatchSDK e assegnalo al watch target, non all'app iOS. Il lato iOS riceve già il plugin tramite cap sync.

Passaggio 3: Scrivi l'app watch in SwiftUI

Il SDK espone WatchConnector.shared, un ObservableObject con i pubblicati isReachable, isActivated, lastMessage, e applicationContext proprietà pubblicate. Attivalo all'avvio dell'app:

import SwiftUI
import CapgoWatchSDK

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

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

Un piccolo schermo che mostra i dati inviati dal telefono e rimanda un'azione:

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

Il async variante di sendMessage invia con un reply handler, in modo che il telefono lo riceva come messageReceivedWithReply evento. La variante basata su callback sendMessage(_:replyHandler:errorHandler:) ti permette di saltare la risposta.

Se preferisci i delegate callback rispetto all'osservazione delle proprietà pubblicate, conforma a WatchConnectorDelegate e imposta WatchConnector.shared.delegate. Ha metodi per messaggi, messaggi con reply handler, contesto dell'applicazione, informazioni utente, reachability e attivazione.

Passaggio 4: Comunicare con il watch da TypeScript

Registra i listener in anticipo, ad esempio nel bootstrap della tua app, così da non perdere gli eventi che arrivano all'avvio.

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

Rispondi sempre a messageReceivedWithReply. L'orologio è in attesa e una richiesta non risposta termina con un errore di timeout sul lato orologio.

Quale metodo di trasferimento scegliere?

WatchConnectivity ti offre tre canali, e il plugin mappa ciascuno di essi:

Metodo Consegna Richiede che il watch sia raggiungibile Ideale per
sendMessage Immediata Sì, altrimenti rifiuta Tap sui pulsanti, aggiornamenti live con entrambe le app aperte
updateApplicationContext Vince l'ultimo valore, consegnato quando possibile No Stato attuale: conteggi, impostazioni, utente loggato
transferUserInfo In coda, consegnati in ordine No Eventi che devono arrivare tutti: allenamenti registrati, record creati

Un pattern che funziona bene: usa il contesto dell'applicazione come fonte di verità per ciò che l'orologio visualizza, usa sendMessage solo per le interazioni mentre l'app dell'orologio è aperta, e usa transferUserInfo per tutto ciò che non puoi permetterti di perdere.

Limiti da considerare nel design

  • Dimensione del payload. WatchConnectivity rifiuta i payload troppo grandi con payloadTooLarge, e Apple non pubblica un numero di byte fisso per ogni canale. Mantieni i payload piccoli: ID, conteggi, stringhe brevi. Recupera i contenuti grandi dal tuo API invece.
  • Tipi di payload. I dati devono essere compatibili con property list: stringhe, numeri, booleani, date, dati, array e dizionari. null I valori da JavaScript non sono validi. Rimuovili prima di inviarli.
  • Il telefono deve avere l'app in esecuzione per gestire gli eventi. I listener risiedono nel runtime JavaScript di Capacitor. Se iOS ha terminato l'app, gli eventi non raggiungeranno il tuo TypeScript finché l'app non riparte. Il contesto e le info utente restano in coda nel sistema, ma progetta la UI dell'orologio perché funzioni con il contesto in cache.
  • Accoppiamento. Un Apple Watch si accoppia solo con un iPhone. Gli utenti Android non possono usarlo, e l'implementazione Android del plugin punta a Wear OS, descritta nella nostra guida a Wear OS.
  • Indipendenza. Un'app companion basata su WatchConnectivity dipende dal telefono. Se l'orologio deve funzionare lontano dal telefono, aggiungi una seconda sorgente dati come il tuo API sulla connessione di rete dell'orologio.

Test su simulatori e dispositivi

Puoi iterare sul layout SwiftUI nel simulatore dell'orologio, ma il trasferimento dati richiede dispositivi accoppiati.

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

Esegui l'app iOS sul simulatore dell'iPhone accoppiato, poi esegui lo scheme dell'orologio sul simulatore dell'orologio. Entrambe le app devono essere installate prima isWatchAppInstalled diventa true. Apple segnala che il Simulator non supporta transferUserInfo, quindi conferma quel percorso su hardware reale.

Per i dispositivi, collega l'iPhone, assicurati che il watch abbia attiva la modalità sviluppatore e seleziona il watch come destinazione di esecuzione per lo scheme del watch. Usa getInfo() come smoke test:

getInfo() risultato Significato
isPaired: false Nessun watch abbinato a questo telefono
isWatchAppInstalled: false App watch mancante, oppure bundle ID / impostazione companion errata
isReachable: false, resto true App watch non in primo piano o fuori portata
activationState: 2, tutti true Pronto per sendMessage

Risoluzione problemi

isWatchAppInstalled resta false. Controlla il prefisso del bundle ID dell'orologio, verifica che il target dell'orologio sia incorporato nel App target (Build Phases > Embed Watch Content) e reinstalla entrambe le app.

I messaggi dall'orologio non raggiungono mai JavaScript. Registra i listener prima che l'orologio invii e conferma che l'orologio chiama WatchConnector.shared.activate() all'avvio. Sul telefono, controlla la console di Xcode per il log di attivazione del plugin.

sendMessage rifiuta con “Watch is not reachable”. Atteso quando l'app dell'orologio è chiusa. Usa updateApplicationContext.

replyToMessage sembra ignorato. Passa esattamente callbackId dall'evento e rispondi rapidamente. Il handler di risposta dell'orologio ha un timeout.

La build fallisce con “Embedded binary’s bundle identifier is not prefixed”. L'ID bundle per watch deve iniziare con l'ID bundle iOS.

Upload dell'archivio rifiutato per icone mancanti. I target watch richiedono un'icona app dedicata in Assets.xcassets.

Pubblicazione dell'app per Apple Watch

L'app per Watch non è un prodotto separato. È integrata nell'archivio iOS, caricata insieme e mostrata nello stesso record di App Store Connect, dove aggiungi gli screenshot di Apple Watch nella scheda watch. Qualunque strumento generi il tuo archivio, che sia Xcode locale, una CI o Build Capgo, necessita di un provisioning profile per entrambi i bundle ID e deve usare Xcode 26.

Tieni presente una cosa sugli update. Live updates di Capgo può distribuire modifiche al web bundle del telefono senza passare dalla revisione dello store, il che fa comodo quando il lato TypeScript del tuo bridge watch ha un bug. Il codice Swift code sul watch, e qualsiasi modifica al plugin nativo, passa comunque dall'App Store. Versiona il formato dei messaggi, ad esempio con un v campo in ogni dictionary, così un bundle del telefono più recente continua a funzionare con una build watch più vecchia.

In sintesi

La divisione è semplice una volta accettata: SwiftUI per il polso, Capacitor per tutto il resto, e tre canali WatchConnectivity in mezzo. Installa @capgo/capacitor-watch, aggiungi il target watch con CapgoWatchSDK, mantieni i payload leggeri e testa su hardware accoppiato prima di inviare.

Live updates per le app Capacitor

Quando un bug nel web-layer è live, rilascia la fix tramite Capgo invece di aspettare giorni l'approvazione degli store. Gli utenti ricevono l'update in background, mentre le modifiche native restano nel normale processo di review.

Ultimi articoli dal blog

Capgo ti offre gli insight migliori che ti servono per creare un'app mobile davvero professionale.