Creating a watchOS App
Copy a setup prompt with the install steps and the full markdown guide for this plugin.
This guide walks you through creating a watchOS companion app from scratch, including project setup in Xcode, integrating the CapgoWatchSDK, and building a functional watch app with SwiftUI.
Prerequisites
Section titled “Prerequisites”Before you begin, ensure you have:
- Xcode 15 or later (download from Mac App Store)
- macOS Sonoma or later (for latest watchOS SDK)
- An existing Capacitor iOS project (run
npx cap add iosif you haven’t) - Apple Developer account (free account works for development)
Project Structure Overview
Section titled “Project Structure Overview”After completing this guide, your project will have this structure:
Directoryios/
DirectoryApp/
DirectoryApp/ (your main iOS app)
- …
- App.xcodeproj
- App.xcworkspace (use this to open the project)
- Podfile
DirectoryMyWatch/ (new watch app)
DirectoryMyWatch/ (watch app source)
- MyWatchApp.swift
- ContentView.swift
DirectoryAssets.xcassets/
- …
- MyWatch.xcodeproj
Step 1: Open Your iOS Project in Xcode
Section titled “Step 1: Open Your iOS Project in Xcode”- Navigate to your Capacitor project’s
ios/Appfolder - Open
App.xcworkspace(not.xcodeproj) by double-clicking it - Wait for Xcode to index the project
Step 2: Add a watchOS Target
Section titled “Step 2: Add a watchOS Target”-
In Xcode, go to File → New → Target…
-
In the template chooser:
- Select watchOS tab at the top
- Choose App
- Click Next
-
Configure your watch app:
- Product Name:
MyWatch(or your preferred name) - Team: Select your Apple Developer team
- Organization Identifier: Should match your iOS app (e.g.,
app.capgo) - Bundle Identifier: Will be auto-generated (e.g.,
app.capgo.myapp.watchkitapp) - Language: Swift
- User Interface: SwiftUI
- Watch App Type: App (not App for Existing iOS App)
- Uncheck Include Notification Scene (unless you need it)
- Uncheck Include Complication (unless you need it)
- Product Name:
-
Click Finish
-
When prompted “Activate ‘MyWatch’ scheme?”, click Activate
Step 3: Configure Watch App Settings
Section titled “Step 3: Configure Watch App Settings”-
In the Project Navigator (left sidebar), select your project (the blue icon at the top)
-
Select your watch target (e.g., “MyWatch”) from the targets list
-
Go to the General tab:
- Display Name: The name shown under the app icon (e.g., “My App”)
- Bundle Identifier: Should end with
.watchkitapp - Version: Match your iOS app version
- Build: Match your iOS app build number
-
Go to Signing & Capabilities tab:
- Enable Automatically manage signing
- Select your Team
- Xcode will create provisioning profiles automatically
-
Set Deployment Info:
- Minimum Deployments: watchOS 9.0 or later
Step 4: Add CapgoWatchSDK via Swift Package Manager
Section titled “Step 4: Add CapgoWatchSDK via Swift Package Manager”The CapgoWatchSDK provides a ready-to-use WatchConnector class for communication.
-
In Xcode, go to File → Add Package Dependencies…
-
In the search field, enter:
https://github.com/Cap-go/capacitor-watch.git -
Press Enter and wait for Xcode to fetch the package
-
Configure the package:
- Dependency Rule: “Up to Next Major Version” with “8.0.0”
- Click Add Package
-
Choose which products to add:
- IMPORTANT: Only select
CapgoWatchSDK - Make sure it’s added to your watch target (e.g., “MyWatch”), not the iOS app
- Click Add Package
- IMPORTANT: Only select
Step 5: Implement the Watch App
Section titled “Step 5: Implement the Watch App”Now let’s create the watch app code. Replace the auto-generated files with the following:
5.1 Create the App Entry Point
Section titled “5.1 Create the App Entry Point”Edit MyWatch/MyWatchApp.swift:
import SwiftUIimport CapgoWatchSDK
@mainstruct MyWatchApp: App { init() { // Activate WatchConnectivity when app launches WatchConnector.shared.activate() }
var body: some Scene { WindowGroup { ContentView() } }}5.2 Create the Main View
Section titled “5.2 Create the Main View”Edit MyWatch/ContentView.swift:
import SwiftUIimport CapgoWatchSDK
struct ContentView: View { // Observe the WatchConnector for automatic UI updates @ObservedObject var connector = WatchConnector.shared
// Local state @State private var messageText = "" @State private var statusMessage = "Ready"
var body: some View { ScrollView { VStack(spacing: 16) { // Connection Status ConnectionStatusView(connector: connector)
Divider()
// Message Input TextField("Message", text: $messageText) .textFieldStyle(.roundedBorder)
// Send Buttons HStack { Button("Send") { sendMessage() } .disabled(!connector.isReachable || messageText.isEmpty)
Button("Request") { sendWithReply() } .disabled(!connector.isReachable || messageText.isEmpty) }
Divider()
// Status Text(statusMessage) .font(.caption) .foregroundColor(.secondary)
// Last Received Message if !connector.lastMessage.isEmpty { VStack(alignment: .leading) { Text("Last Message:") .font(.caption) .foregroundColor(.secondary) Text(formatMessage(connector.lastMessage)) .font(.caption2) } .frame(maxWidth: .infinity, alignment: .leading) } } .padding() } }
private func sendMessage() { connector.sendMessage(["text": messageText, "timestamp": Date().timeIntervalSince1970]) statusMessage = "Message sent" messageText = "" }
private func sendWithReply() { connector.sendMessage(["text": messageText, "needsReply": true]) { reply in DispatchQueue.main.async { statusMessage = "Reply: \(formatMessage(reply))" } } messageText = "" }
private func formatMessage(_ message: [String: Any]) -> String { message.map { "\($0.key): \($0.value)" }.joined(separator: ", ") }}
// Separate view for connection statusstruct ConnectionStatusView: View { @ObservedObject var connector: WatchConnector
var body: some View { HStack { Circle() .fill(connector.isReachable ? Color.green : Color.red) .frame(width: 12, height: 12)
Text(connector.isReachable ? "Connected" : "Disconnected") .font(.headline)
Spacer()
if connector.isActivated { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) } } }}
#Preview { ContentView()}Step 6: Configure iOS App for WatchConnectivity
Section titled “Step 6: Configure iOS App for WatchConnectivity”Your iOS app also needs WatchConnectivity capability.
-
In the Project Navigator, select your project
-
Select your iOS App target (not the watch target)
-
Go to Signing & Capabilities tab
-
Click + Capability
-
Search for and add WatchConnectivity (if available) or it may be added automatically
-
The Capacitor plugin handles the iOS side automatically, but ensure your Info.plist has:
<key>WKCompanionAppBundleIdentifier</key><string>app.capgo.myapp.watchkitapp</string>
Step 7: Build and Run
Section titled “Step 7: Build and Run”Run on Simulator
Section titled “Run on Simulator”-
Select your watch scheme from the scheme selector (top of Xcode window)
-
Choose a watch simulator:
- Click on the device selector next to the scheme
- Select an Apple Watch simulator (e.g., “Apple Watch Series 9 (45mm)”)
-
Click the Run button (▶️) or press
Cmd + R -
The iOS Simulator will launch with both iPhone and Apple Watch
Run on Physical Device
Section titled “Run on Physical Device”-
Connect your iPhone via USB
-
Ensure your Apple Watch is paired with that iPhone
-
Select your watch scheme
-
Select your physical Apple Watch from the device list
-
Click Run
-
First time: You may need to trust your computer on both devices
Step 8: Test Communication
Section titled “Step 8: Test Communication”From iPhone (Capacitor) to Watch
Section titled “From iPhone (Capacitor) to Watch”In your Capacitor app:
import { CapgoWatch } from '@capgo/capacitor-watch';
// Check connectionconst info = await CapgoWatch.getInfo();console.log('Watch reachable:', info.isReachable);
// Send a messageif (info.isReachable) { await CapgoWatch.sendMessage({ data: { action: 'update', value: 'Hello from iPhone!' } });}From Watch to iPhone
Section titled “From Watch to iPhone”The watch app uses WatchConnector:
// Send message (fire and forget)WatchConnector.shared.sendMessage(["action": "buttonTapped"])
// Send message with replyWatchConnector.shared.sendMessage(["request": "getData"]) { reply in print("Got reply: \(reply)")}Handle Messages on iPhone
Section titled “Handle Messages on iPhone”// Listen for messages from watchawait CapgoWatch.addListener('messageReceived', (event) => { console.log('Message from watch:', event.message); // { action: 'buttonTapped' }});
// Handle messages that need a replyawait CapgoWatch.addListener('messageReceivedWithReply', async (event) => { console.log('Request from watch:', event.message);
// Send reply back await CapgoWatch.replyToMessage({ callbackId: event.callbackId, data: { status: 'success', items: ['item1', 'item2'] } });});Advanced: Custom Delegate for More Control
Section titled “Advanced: Custom Delegate for More Control”If you need more control, implement WatchConnectorDelegate:
import SwiftUIimport CapgoWatchSDK
class WatchHandler: WatchConnectorDelegate { func didReceiveMessage(_ message: [String: Any]) { print("Received: \(message)") // Handle incoming message }
func didReceiveMessageWithReply(_ message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) { print("Received request: \(message)") // Process and send reply replyHandler(["status": "processed"]) }
func didReceiveApplicationContext(_ context: [String: Any]) { print("Context updated: \(context)") }
func didReceiveUserInfo(_ userInfo: [String: Any]) { print("User info received: \(userInfo)") }
func reachabilityDidChange(_ isReachable: Bool) { print("Reachability changed: \(isReachable)") }
func activationDidComplete(with state: WCSessionActivationState) { print("Activation completed: \(state.rawValue)") }}
// In your app setup:let handler = WatchHandler()WatchConnector.shared.delegate = handlerWatchConnector.shared.activate()Troubleshooting
Section titled “Troubleshooting”Watch App Not Appearing on Watch
Section titled “Watch App Not Appearing on Watch”- Ensure bundle IDs are correctly related (watch app bundle ID should be iOS app bundle ID +
.watchkitapp) - Check that both apps are signed with the same team
- On physical device: Open Watch app on iPhone → My Watch → scroll to find your app → toggle ON
Messages Not Being Received
Section titled “Messages Not Being Received”- Verify both apps have WCSession activated
- Check
isReachablebefore sending messages - For guaranteed delivery, use
transferUserInfoinstead ofsendMessage - Ensure listeners are registered before the other device sends messages
”Session Not Activated” Error
Section titled “”Session Not Activated” Error”- Call
WatchConnector.shared.activate()early in app lifecycle - On iOS, the plugin activates automatically - ensure plugin is imported
- Check that WatchConnectivity capability is added to iOS target
Build Errors with CapgoWatchSDK
Section titled “Build Errors with CapgoWatchSDK”- Ensure the package is added to the watch target, not iOS target
- Clean build folder: Product → Clean Build Folder (Cmd + Shift + K)
- Reset package caches: File → Packages → Reset Package Caches
Simulator Issues
Section titled “Simulator Issues”- Reset the simulators: Device → Erase All Content and Settings
- Ensure iOS and watchOS simulators are compatible pairs
- Both simulators need to be running for communication to work
Next Steps
Section titled “Next Steps”- API Reference - Complete API documentation
- Communication Patterns - When to use each method
- Example App - Full working example
Keep going from Creating a watchOS App
Section titled “Keep going from Creating a watchOS App”If you are using Creating a watchOS App to plan native plugin work, connect it with Using @capgo/capacitor-watch for the native capability in Using @capgo/capacitor-watch, Capgo Plugin Directory for the product workflow in Capgo Plugin Directory, Capacitor Plugins by Capgo for the implementation detail in Capacitor Plugins by Capgo, Adding or Updating Plugins for the implementation detail in Adding or Updating Plugins, and Ionic Enterprise Plugin Alternatives for the product workflow in Ionic Enterprise Plugin Alternatives.