SkillAgentSearch skills...

WatchConnectivitySwift

A modern async/await based convenience package on top of Apple's WatchConnectivity framework

Install / Use

/learn @ts95/WatchConnectivitySwift
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

WatchConnectivitySwift

Version Swift iOS watchOS License

WatchConnectivitySwift is a modern, type-safe Swift library that simplifies communication between iOS and watchOS using Apple's WatchConnectivity framework. Built from the ground up with Swift 6 strict concurrency, async/await, and strong typing, it enables you to write robust, reactive, and testable communication layers between your iPhone and Apple Watch apps.


Features

  • Type-safe request/response using protocols with associated types
  • Swift 6 strict concurrency compliant with @MainActor isolation
  • Automatic retry with configurable retry policies for reliable message delivery
  • Fallback delivery strategies (message -> userInfo -> context)
  • File transfers with progress tracking and async/await support
  • Shared state synchronization via applicationContext
  • Session health monitoring with automatic recovery
  • SwiftUI integration via ObservableObject with @Published properties
  • Comprehensive diagnostics via AsyncStream events

Requirements

| Platform | Minimum Version | |----------|-----------------| | iOS | 17.0+ | | watchOS | 10.0+ | | Swift | 6.0+ | | Xcode | 16.0+ |


Installation

Swift Package Manager

Add the following to your Package.swift:

dependencies: [
    .package(url: "https://github.com/ts95/WatchConnectivitySwift.git", from: "5.2.0")
]

Or in Xcode: File > Add Package Dependencies, then enter the repository URL.


Quick Start

1. Define Your Request Types (Shared Code)

Create a shared Swift file or framework that both your iOS and watchOS targets can access. This ensures both sides understand the same request/response types.

// 📁 Shared/WatchRequests.swift
// ⚠️ This file must be included in BOTH your iOS and watchOS targets,
//    or placed in a shared framework that both targets depend on.

import WatchConnectivitySwift

// Define a request that the Watch sends to the iPhone.
// The iPhone will handle this and return a Recipe.
struct FetchRecipeRequest: WatchRequest {
    // The response type that the handler must return
    typealias Response = Recipe

    let recipeID: String
}

// The response model - must be Codable for serialization over the wire
struct Recipe: Codable, Sendable {
    let title: String
    let ingredients: [String]
}

2. Register Handlers on iPhone (iOS Target)

On the iOS side, register handlers for requests that the Watch will send. The iPhone acts as the "server" that responds to Watch requests.

// 📁 iOS App/AppCoordinator.swift
// 💡 This code runs ONLY on the iPhone

import WatchConnectivitySwift

@MainActor
class AppCoordinator {
    // Create a single WatchConnection instance for your iOS app
    let connection = WatchConnection()

    func setup() {
        // Register a handler for FetchRecipeRequest.
        // When the Watch sends this request, this closure is called
        // and the returned Recipe is sent back to the Watch.
        connection.register(FetchRecipeRequest.self) { request in
            // Access your iOS app's data sources here:
            // - Core Data, SwiftData, Realm
            // - Network APIs, Firestore
            // - UserDefaults, Keychain, etc.
            return Recipe(
                title: "Pasta Carbonara",
                ingredients: ["Pasta", "Eggs", "Pancetta", "Parmesan"]
            )
        }
    }
}

3. Send Requests from Watch (watchOS Target)

On the watchOS side, send requests to the iPhone and await the typed response.

// 📁 watchOS App/RecipeViewModel.swift
// 💡 This code runs ONLY on the Apple Watch

import WatchConnectivitySwift

@MainActor
class RecipeViewModel: ObservableObject {
    @Published var recipe: Recipe?
    @Published var error: Error?

    // Create a single WatchConnection instance for your watchOS app
    private let connection = WatchConnection()

    func loadRecipe(id: String) async {
        do {
            // Send the request to the iPhone and await the response.
            // The iPhone's registered handler will process this and return a Recipe.
            recipe = try await connection.send(
                FetchRecipeRequest(recipeID: id),
                strategy: .messageWithUserInfoFallback  // Falls back to queued delivery if unreachable
            )
        } catch {
            self.error = error
        }
    }
}

Note: Communication can go both ways. The Watch can also register handlers, and the iPhone can send requests to the Watch using the same pattern.


Core Concepts

Request Types

// 📁 Shared/Requests.swift
// ⚠️ Place in shared code accessible by both iOS and watchOS targets

// Standard request expecting a response
struct MyRequest: WatchRequest {
    typealias Response = MyResponse
    let data: String
}

struct MyResponse: Codable, Sendable {
    let result: String
}

// Fire-and-forget request (no response expected)
// Useful for logging, analytics, or notifications where you don't need confirmation
struct LogEventRequest: FireAndForgetRequest {
    let eventName: String
}

Delivery Strategies

Choose how messages are delivered based on your reliability needs. WatchConnectivity provides three transport mechanisms with different tradeoffs:

| Transport | Speed | Reliability | Behavior | |-----------|-------|-------------|----------| | sendMessage | Instant | May fail | Requires counterpart app to be reachable | | transferUserInfo | Queued | Guaranteed | Delivers in order when app becomes active | | applicationContext | Queued | Guaranteed | Only latest value delivered (overwrites pending) |

Available strategies:

// DEFAULT: Best for most use cases
// Instant delivery when possible, queued backup when not
.messageWithUserInfoFallback

// For settings/state where only latest value matters
// If you send 5 updates while offline, only the last one is delivered
.messageWithContextFallback

// For real-time features only (remote control, live updates)
// Fails immediately if counterpart is unreachable
.messageOnly

// For background sync where order matters
// All messages queued and delivered in order, even if app is suspended
.userInfoOnly

// For state sync where only current value matters
// Overwrites any pending value not yet delivered
.contextOnly

When to use each:

| Strategy | Use Case | |----------|----------| | messageWithUserInfoFallback | Chat messages, notifications, data requests—anything that must eventually arrive | | messageWithContextFallback | Settings sync, preferences, status updates where stale values are useless | | messageOnly | Remote camera shutter, live game controls, time-sensitive actions | | userInfoOnly | Workout logs, transaction history, audit trails that need ordering | | contextOnly | Current user state, now-playing info, connection status |

Retry Policies

Configure retry behavior for transient failures:

// Built-in policies
.default     // 3 attempts, 10s timeout, 200ms delay between retries
.patient     // 5 attempts, 30s timeout, 200ms delay between retries
.none        // 1 attempt, no retries

// Custom policy
RetryPolicy(maxAttempts: 4, timeout: .seconds(15))

Shared State

Synchronize state between devices. SharedState uses applicationContext under the hood, which automatically syncs the latest value to the counterpart device.

// 📁 Shared/AppSettings.swift
// ⚠️ The model must be in shared code accessible by both targets

struct AppSettings: Codable, Sendable, Equatable {
    var theme: String
    var notificationsEnabled: Bool
}
// 📁 iOS App/SettingsManager.swift  (or watchOS App/SettingsManager.swift)
// 💡 Use the same pattern on BOTH iOS and watchOS targets.
//    Each side creates its own SharedState instance with the same structure.
//    Updates from either side automatically sync to the other.

import WatchConnectivitySwift

@MainActor
class SettingsManager {
    let sharedSettings: SharedState<AppSettings>

    init(connection: WatchConnection) {
        // Both iOS and watchOS create a SharedState with the same initial value.
        // The library handles syncing updates between devices automatically.
        sharedSettings = SharedState(
            initialValue: AppSettings(theme: "light", notificationsEnabled: true),
            connection: connection  // Pass the WatchConnection instance
        )
    }

    func updateTheme(_ theme: String) throws {
        // Update locally - the change is automatically pushed to the other device
        var settings = sharedSettings.value
        settings.theme = theme
        try sharedSettings.update(settings)
    }
}

### File Transfers

Transfer files between devices with progress tracking. Files are transferred in the background and continue even when your app is suspended.

```swift
// 📁 iOS App/FileTransferManager.swift  (or watchOS App/FileTransferManager.swift)
// 💡 File transfers work in both directions between iOS and watchOS

import WatchConnectivitySwift

@MainActor
class FileTransferManager {
    private let connection = WatchConnection()

    // MARK: - Sending Files

    func sendFile(_ fileURL: URL, metadata: [String: Any]? = nil) async throws {
        // Start the transfer and get a FileTransfer object for tracking
        let transfer = try await connection.transferFile(fileURL, metadata: metadata)

        // Option 1: Wait for completion
        try await transfer.waitForCompletion()

        // Option 2: Track progress
        for await progress in transfer.progressUpdates {
View on GitHub
GitHub Stars5
CategoryDevelopment
Updated1mo ago
Forks1

Languages

Swift

Security Score

90/100

Audited on Feb 10, 2026

No findings