This guide walks you through setting up WatchConnectivitySwift in your iOS and watchOS apps.
Add the following dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/ts95/WatchConnectivitySwift.git", from: "5.1.0")
]Then add WatchConnectivitySwift to your target's dependencies:
.target(
name: "YourApp",
dependencies: ["WatchConnectivitySwift"]
)- Open your project in Xcode
- Go to File > Add Package Dependencies
- Enter the repository URL:
https://github.com/ts95/WatchConnectivitySwift.git - Select version 5.1.0 or later
- Add the package to both your iOS and watchOS targets
Create a shared Swift file accessible to both your iOS and watchOS targets:
import WatchConnectivitySwift
// A request that expects a response
struct FetchUserRequest: WatchRequest {
typealias Response = User
let userID: String
}
struct User: Codable, Sendable {
let id: String
let name: String
let email: String
}
// A fire-and-forget request (no response)
struct LogAnalyticsEvent: FireAndForgetRequest {
let eventName: String
let parameters: [String: String]
}On both iOS and watchOS, create a WatchConnection instance. Typically, you'd store this as a singleton or in your app's environment:
import WatchConnectivitySwift
@MainActor
class AppCoordinator: ObservableObject {
static let shared = AppCoordinator()
let connection = WatchConnection()
private init() {
setupHandlers()
}
}On the device that will receive requests, register handlers:
// On iPhone - handling requests from Watch
func setupHandlers() {
connection.register(FetchUserRequest.self) { request in
// Fetch user from your backend, database, etc.
let user = try await UserService.fetchUser(id: request.userID)
return user
}
connection.register(LogAnalyticsEvent.self) { request in
Analytics.log(event: request.eventName, parameters: request.parameters)
return VoidResponse()
}
}On the device that will send requests:
// On Watch - sending requests to iPhone
@MainActor
class UserViewModel: ObservableObject {
@Published var user: User?
@Published var error: Error?
private let connection: WatchConnection
init(connection: WatchConnection = AppCoordinator.shared.connection) {
self.connection = connection
}
func loadUser(id: String) async {
do {
user = try await connection.send(FetchUserRequest(userID: id))
} catch {
self.error = error
}
}
func logEvent(_ name: String) async {
await connection.send(LogAnalyticsEvent(eventName: name, parameters: [:]))
}
}struct UserView: View {
@StateObject private var viewModel = UserViewModel()
var body: some View {
VStack {
if let user = viewModel.user {
Text(user.name)
Text(user.email)
.font(.caption)
} else if let error = viewModel.error {
Text("Error: \(error.localizedDescription)")
.foregroundColor(.red)
} else {
ProgressView()
}
}
.task {
await viewModel.loadUser(id: "123")
}
}
}struct ConnectionStatusView: View {
@ObservedObject var connection: WatchConnection
var body: some View {
HStack {
Circle()
.fill(connection.isReachable ? Color.green : Color.red)
.frame(width: 10, height: 10)
Text(connection.isReachable ? "Connected" : "Disconnected")
}
}
}Choose how messages are delivered:
// Default: try message, fall back to userInfo queue
let response = try await connection.send(request)
// For state that only needs latest value
let response = try await connection.send(
request,
strategy: .messageWithContextFallback
)
// Real-time only (fail if not reachable)
let response = try await connection.send(
request,
strategy: .messageOnly
)Configure retry behavior:
// Use built-in policies
let response = try await connection.send(request, retryPolicy: .patient) // 5 attempts, 30s timeout
let response = try await connection.send(request, retryPolicy: .none) // No retries
// Or create custom policies
let customPolicy = RetryPolicy(maxAttempts: 4, timeout: .seconds(15))
let response = try await connection.send(request, retryPolicy: customPolicy)Control when requests are sent:
// Queue if not reachable (default)
let response = try await connection.send(request, delivery: .queued)
// Fail immediately if not reachable
let response = try await connection.send(request, delivery: .immediate)Transfer files between devices with progress tracking:
@MainActor
class DocumentSender {
private let connection: WatchConnection
func sendDocument(_ fileURL: URL) async throws {
// Start the transfer
let transfer = try await connection.transferFile(
fileURL,
metadata: ["type": "document", "name": fileURL.lastPathComponent]
)
// Track progress (optional)
for await progress in transfer.progressUpdates {
print("Uploading: \(Int(progress * 100))%")
}
// Or just wait for completion
try await transfer.waitForCompletion()
}
// Cancel a transfer
func cancelTransfer(_ transfer: FileTransfer) {
transfer.cancel()
}
// Cancel all transfers
func cancelAll() {
connection.cancelAllFileTransfers()
}
}@MainActor
class DocumentReceiver {
private let connection: WatchConnection
func setup() {
// Option 1: Callback
connection.onFileReceived = { [weak self] file in
self?.handleReceivedFile(file)
}
// Option 2: Async stream
Task {
for await file in connection.receivedFiles {
handleReceivedFile(file)
}
}
}
private func handleReceivedFile(_ file: ReceivedFile) {
// ⚠️ IMPORTANT: Move file synchronously!
// It will be deleted after this method returns
let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let destination = documents.appendingPathComponent(file.fileURL.lastPathComponent)
do {
try FileManager.default.moveItem(at: file.fileURL, to: destination)
print("Saved file to: \(destination)")
// Access metadata
if let type = file.metadata?["type"] as? String {
print("File type: \(type)")
}
} catch {
print("Failed to save file: \(error)")
}
}
}// Check active transfers
let activeTransfers = connection.outstandingFileTransfers
print("Active transfers: \(activeTransfers.count)")
// Observe transfer count changes via @Published property
// connection.activeFileTransferCount- Read the Architecture Overview to understand how the library works
- Learn about Shared State for synchronizing data
- Explore Reliability Features for handling connectivity issues
- See the API Reference for complete documentation