Untuk membangun aplikasi Apple Watch untuk aplikasi Capacitor, Anda menambahkan target watchOS native ke proyek iOS Anda, menulis UI-nya dalam SwiftUI, dan menghubungkannya ke code web Anda dengan @capgo/capacitor-watch plugin, yang membungkus framework WatchConnectivity dari Apple. Aplikasi ponsel tetap mempertahankan lapisan web Capacitor-nya, dan aplikasi watch menjadi pendamping native kecil yang mengirim dan menerima dictionary.
Panduan ini membahas alur lengkap pada Capacitor 8 dan Xcode 26: arsitektur, Xcode target, code Swift dan TypeScript di masing-masing sisi, metode transfer yang harus dipakai, pengujian di simulator dan perangkat, serta cara watch app didistribusikan.
Mengapa UI watch bersifat native dan ponsel tetap Capacitor
watchOS tidak menyertakan WebView. Tidak ada WKWebView di watch, jadi app Capacitor tidak bisa berjalan di sana, dan tidak ada plugin yang bisa mengubahnya. Yang bisa Anda lakukan adalah membagi tanggung jawab:
| Lapisan | Berjalan di | Bahasa | Tugas |
|---|---|---|---|
| app Capacitor | iPhone | TypeScript, framework pilihan Anda | Logika bisnis, network, auth, storage |
@capgo/capacitor-watch |
iPhone | Swift (plugin), JS API | Menghubungkan WatchConnectivity ke JavaScript |
| CapgoWatchSDK | Apple Watch | Swift | Penanganan sesi di watch |
| Aplikasi Watch | Apple Watch | SwiftUI | Layar kecil, UI yang mudah dipantau, aksi cepat |
Dalam praktiknya, aplikasi watch bersifat “tergantung”: datanya diambil dari ponsel. Simpan logika di ponsel di mana sudah ada dalam TypeScript, dan kirim ke watch hanya apa yang dibutuhkan untuk ditampilkan.
Prerequisites
- Aplikasi Capacitor 8 dengan platform iOS yang sudah ditambahkan. Jika Anda masih menggunakan versi lama, ikuti panduan upgrade Capacitor 8 pertama.
- Xcode 26 di Mac. Apple mewajibkan Xcode 26 untuk upload ke App Store sejak April 2026, lihat persyaratan Xcode 26.
- Akun Apple Developer untuk pengujian perangkat dan distribusi.
- iOS 15 atau lebih baru di ponsel (minimum plugin) dan watchOS 9 atau lebih baru di watch (minimum SDK).
- Idealnya, iPhone asli yang dipasangkan dengan Apple Watch asli. Simulator membantu, tetapi tidak mencakup semua jenis transfer.
Langkah 1: Instal plugin di sisi ponsel
bun add @capgo/capacitor-watch
bunx cap sync ios
Plugin diaktifkan WCSession segera setelah dimuat, jadi tidak ada panggilan init. Mulai dengan membaca status koneksi:
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
});
Referensi lengkap API ada di dokumentasi plugin dan halaman plugin.
Langkah 2: Tambahkan target watchOS di Xcode
Buka proyek iOS dengan bunx cap open ios. Proyek Capacitor 8 yang dibuat dengan Swift Package Manager dibuka App.xcodeproj; proyek CocoaPods lama dibuka App.xcworkspace. Keduanya bisa digunakan.
- Pilih File > New > Target, select the watchOS tab, lalu App.
- Beri nama, misalnya
MyWatch, dan gunakan SwiftUI sebagai interface. - Di sheet opsi, pastikan watch app sudah di-embed ke dalam
Apptarget yang ada sebagai companion-nya. Pengaturan inilah yang membuat Xcode meng-embed watch app ke build iPhone dan mengatur companion bundle identifier. - Biarkan notification scenes dan complications nonaktif kecuali Anda membutuhkannya sekarang. Anda bisa menambahkan widget nanti.
- Setujui prompt untuk mengaktifkan scheme baru.
Aturan bundle identifier
Bundle ID aplikasi watch harus diawali dengan bundle ID aplikasi iPhone. Jika aplikasi Anda adalah com.example.shop, Xcode akan mengusulkan com.example.shop.watchkitapp. Pertahankan prefix tersebut. Jika Anda mengubah bundle ID iOS nanti, ubah juga yang untuk watch, atau build akan gagal validasi dan WatchConnectivity tidak akan memasangkan kedua aplikasi.
Di bawah Signing & Capabilities, pilih tim yang sama untuk kedua target. Dengan penandatanganan otomatis, Xcode akan mendaftarkan App ID dan provisioning profile kedua untuk Anda.
Tambahkan CapgoWatchSDK ke target watch
- File > Tambahkan Dependensi Paket.
- Masukkan
https://github.com/Cap-go/capacitor-watch.git. - Gunakan “Up to Next Major Version” dari
8.0.0. - Saat Xcode bertanya produk mana yang akan ditambahkan, pilih hanya CapgoWatchSDK dan tetapkan ke watch target, bukan aplikasi iOS. Sisi iOS sudah mendapatkan plugin melalui
cap sync.
Langkah 3: Tulis aplikasi watch dalam SwiftUI
SDK mengekspos WatchConnector.shared, sebuah ObservableObject dengan yang sudah dipublikasikan isReachable, isActivated, lastMessage, dan applicationContext properties yang dipublikasikan. Aktifkan saat aplikasi dimulai:
import SwiftUI
import CapgoWatchSDK
@main
struct MyWatchApp: App {
init() {
WatchConnector.shared.activate()
}
var body: some Scene {
WindowGroup {
OrdersView()
}
}
}
Layar kecil yang menampilkan data yang dikirim dari ponsel dan mengirim aksi balik:
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"
}
}
}
}
Panduan async varian dari sendMessage dikirim dengan reply handler, sehingga ponsel menerimanya sebagai messageReceivedWithReply event. Varian berbasis callback sendMessage(_:replyHandler:errorHandler:) memungkinkan Anda melewati reply tersebut.
Jika Anda lebih suka delegate callback daripada mengamati published properties, gunakan WatchConnectorDelegate dan atur WatchConnector.shared.delegate. Ini punya metode untuk messages, messages dengan reply handler, application context, user info, reachability, dan activation.
Langkah 4: Komunikasi dengan watch dari TypeScript
Daftarkan listener lebih awal, misalnya di bootstrap app Anda, agar event yang tiba saat launch tidak terlewat.
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() },
});
}
Selalu balas ke messageReceivedWithReply. Watch sedang menunggu, dan request yang tidak dijawab berakhir dengan timeout error di sisi watch.
Metode transfer mana yang sebaiknya Anda gunakan?
WatchConnectivity menyediakan tiga channel, dan plugin memetakan masing-masingnya:
| Metode | Pengiriman | Watch harus dapat dijangkau | Cocok untuk |
|---|---|---|---|
sendMessage |
Segera | Ya, selain itu ditolak | Ketukan tombol, live update saat kedua aplikasi terbuka |
updateApplicationContext |
Nilai terbaru menang, dikirim saat memungkinkan | Tidak | Status saat ini: jumlah, pengaturan, user yang login |
transferUserInfo |
Diantrekan, dikirim sesuai urutan | Tidak | Event yang semuanya harus sampai: workout yang dicatat, record yang dibuat |
Pola yang bekerja dengan baik: gunakan application context sebagai source of truth untuk apa yang ditampilkan watch, gunakan sendMessage hanya untuk interaksi selama app watch terbuka, dan gunakan transferUserInfo untuk apa pun yang tidak boleh hilang.
Batasan dalam desain
- Ukuran payload. WatchConnectivity menolak payload yang terlalu besar dengan
payloadTooLarge, dan Apple tidak memublikasikan jumlah byte tetap untuk setiap channel. Jaga payload tetap kecil: ID, jumlah, string pendek. Ambil konten besar dari API Anda sebagai gantinya. - Tipe payload. Data harus kompatibel dengan property-list: string, number, boolean, date, data, array, dan dictionary.
nullnilai dari JavaScript tidak valid. Buang nilai itu sebelum dikirim. - Ponsel harus sedang menjalankan app Anda untuk menangani event. Listeners berjalan di runtime JavaScript Capacitor. Jika iOS telah mengakhiri aplikasi, event tidak akan sampai ke TypeScript Anda sampai aplikasi berjalan lagi. Konteks dan info pengguna tetap di-queue oleh sistem, tetapi rancang UI watch agar berfungsi dari konteks yang di-cache.
- Pairing. Apple Watch hanya berpasangan dengan iPhone. Pengguna Android tidak dapat menggunakannya, dan implementasi Android plugin ini menargetkan Wear OS, yang dibahas dalam panduan Wear OS kami.
- Kemandirian. Aplikasi pendamping yang dibangun di atas WatchConnectivity bergantung pada ponsel. Jika watch harus berfungsi jauh dari ponsel, tambahkan sumber data kedua seperti API Anda sendiri melalui koneksi jaringan watch.
Pengujian di simulator dan perangkat
Anda dapat mengiterasi layout SwiftUI di simulator watch, tetapi transfer data memerlukan setup yang berpasangan.
xcrun simctl list devices
xcrun simctl pair <watch-udid> <phone-udid>
Jalankan aplikasi iOS di simulator iPhone yang berpasangan, lalu jalankan skema watch di simulator watch. Kedua aplikasi harus terinstal terlebih dahulu isWatchAppInstalled menjadi true. Apple mencatat bahwa Simulator tidak mendukung transferUserInfo, jadi pastikan path tersebut di perangkat fisik.
Untuk perangkat, hubungkan iPhone, pastikan developer mode pada watch sudah aktif, lalu pilih watch sebagai run destination untuk watch scheme. Gunakan getInfo() sebagai smoke test Anda:
getInfo() hasil |
Arti |
|---|---|
isPaired: false |
Tidak ada watch yang dipasangkan dengan ponsel ini |
isWatchAppInstalled: false |
Watch app tidak ada, atau bundle ID / pengaturan companion salah |
isReachable: false, sisanya true |
Watch app tidak di foreground atau di luar jangkauan |
activationState: 2, semuanya true |
Siap untuk sendMessage |
Pemecahan Masalah
isWatchAppInstalled tetap false. Cek prefix bundle ID watch, pastikan watch target sudah di-embed di dalam App target (Build Phases > Embed Watch Content), lalu instal ulang kedua aplikasi.
Pesan dari jam tidak pernah sampai ke JavaScript. Daftarkan listener sebelum jam mengirim, dan pastikan jam memanggil WatchConnector.shared.activate() saat launch. Di ponsel, periksa konsol Xcode untuk log aktivasi plugin.
sendMessage ditolak dengan “Watch is not reachable”. Diharapkan saat aplikasi jam ditutup. Gunakan fallback ke updateApplicationContext.
replyToMessage tampaknya diabaikan. Teruskan nilai persis callbackId dari event dan balas dengan cepat. Handler balasan di jam memiliki timeout.
Build gagal dengan pesan “Embedded binary’s bundle identifier is not prefixed”. Bundle ID watch harus diawali dengan bundle ID iOS.
Upload archive ditolak karena ikon tidak ada. Target watch membutuhkan ikon aplikasi sendiri di Assets.xcassets.
Menerbitkan aplikasi watch
Aplikasi watch bukan produk terpisah. Ia tertanam dalam arsip iOS, diunggah bersama, dan ditampilkan pada record App Store Connect yang sama, di mana Anda menambahkan screenshot Apple Watch di tab watch. Apa pun yang menghasilkan arsip Anda, Xcode lokal, CI, atau Build Capgo, membutuhkan provisioning profile untuk kedua bundle ID dan harus menggunakan Xcode 26.
Ingat satu hal untuk update. Live updates Capgo bisa mengirim perubahan ke web bundle ponsel tanpa review store, yang berguna ketika sisi TypeScript dari watch bridge Anda memiliki bug. Swift code di watch, dan perubahan apa pun pada native plugin, tetap melalui App Store. Versikan format pesan Anda, misalnya dengan v field di setiap dictionary, agar bundle ponsel yang lebih baru tetap berfungsi dengan build watch yang lebih lama.
Kesimpulan
Pemisahannya sederhana sekali setelah Anda menerimanya: SwiftUI untuk pergelangan tangan, Capacitor untuk semua yang lain, dan tiga kanal WatchConnectivity di antaranya. Install @capgo/capacitor-watch, tambahkan target watch dengan CapgoWatchSDK, pertahankan payload kecil, dan uji pada perangkat yang berpasangan sebelum Anda mengirim.