Skip to content

Taking a Look At WCSession

iOS, WatchOS, WCSession2 min read

The WCSession framework serves as a conduit for data transfer and message passing between a paired Apple Watch and iPhone. It provides a robust set of tools for establishing, managing, and leveraging this communication channel. By utilizing WCSession, developers can create feature-rich applications that seamlessly exchange data and commands between the two devices.

Getting Started with WCSession

To begin using WCSession, the first step is to establish a session between the Apple Watch and iPhone. This involves setting up the session delegate and activating the session on both devices. Once the session is activated, various methods and properties provided by WCSession can be employed to send messages, transfer files, and monitor the state of the session. You can use a custom class for this, I personally used an ObservableObject in one of my SwiftUI apps. Here is the general gist of getting things started:

1// Example of activating WCSession
2if WCSession.isSupported() {
3 let session = WCSession.default
4 session.delegate = self
5 session.activate()

Sending Messages with WCSession

One of the fundamental features of WCSession is the ability to send messages containing data from one device to the other. This can be particularly useful for triggering actions or updating information on the recipient device based on user interactions or real-time data changes.

1// Example of sending a message from iPhone to Apple Watch using WCSession
2if WCSession.default.isReachable {
3 let message = ["key": "value"]
4 WCSession.default.sendMessage(message, replyHandler: { reply in
5 // Handle reply from Apple Watch
6 }, errorHandler: { error in
7 // Handle any errors
8 })

You might potentially want to create a custom struct if your dictionary data gets too complicated. Here's portion of what I used in one of my projects:

1struct SettingsSyncData: Codable, Equatable {
2 let dataA: String
3 let dataB: Double
5 static func decode(data: Data) -> SettingsSyncData? {
6 let decoder = JSONDecoder()
7 return try? decoder.decode(SettingsSyncData.self, from: data)
8 }
10 func encode() -> Data? {
11 let encoder = JSONEncoder()
12 return try? encoder.encode(self)
13 }

Because this has an encode function an is codable you can create the dictionary to send like this:

1func syncSettings(syncData: SettingsSyncData) {
2 guard let data = syncData.encode() else {
3 return
4 }
6 session.sendMessage(["settings_data": data], replyHandler: nil) { error in
7 log.error(error)
8 }

Depending on how critical your data is, you may want to use transferUserInfo instead(this seems to get higher priority than regular messages and sends it in a FIFO fashion). The frustrating thing is that transferUserInfo isn't supported by the simulator so your only real option is to test on a physical device...

If you just want to share state between the two devices, and don't care about previous data then check out applicationContext and updateApplicationContext. It all really comes down to your specific implementation.

Transferring Files with WCSession

In addition to sending messages, WCSession also supports transferring files between the paired devices. This capability enables the seamless exchange of larger datasets, such as media files or documents, contributing to a more integrated and synchronized user experience.

1// Example of transferring a file from iPhone to Apple Watch using WCSession
2if WCSession.default.isReachable {
3 if let fileURL = Bundle.main.url(forResource: "example", withExtension: "txt") {
4 WCSession.default.transferFile(fileURL, metadata: nil)
5 }

Monitoring Session State

WCSession provides mechanisms for monitoring the state of the session, including changes in reachability and activation status. These notifications can be leveraged to adapt the behavior of the application based on the current connectivity between the devices.

1// Example of monitoring session state changes
2NotificationCenter.default.addObserver(forName: WCSession.didBecomeInactiveNotification, object: nil, queue: nil) { _ in
3 // Handle session becoming inactive
6NotificationCenter.default.addObserver(forName: WCSession.didDeactivateNotification, object: nil, queue: nil) { _ in
7 // Handle session deactivation

You can also get a callback for activation by overridding func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?). To override this you'll need to implement WCSessionDelegate in your class:

1class ExampleSyncStore: NSObject, WCSessionDelegate, ObservableObject {
2 #if os(iOS)
3 public func sessionDidBecomeInactive(_ session: WCSession) {}
4 public func sessionDidDeactivate(_ session: WCSession) {}
5 #endif
7 private var session: WCSession = .default
8 var isWatch: Bool
10 init(isWatch: Bool) {
11 self.isWatch = isWatch
12 super.init()
14 if WCSession.isSupported() {
15 self.session.delegate = self
16 self.session.activate()
17 }
18 }
20 func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
21 // Activation stuff goes here
22 }