Episode #252

Watch Connectivity

Series: Up to Speed with watchOS

17 minutes
Published on January 19, 2017

This video is only available to subscribers. Get access to this video and 572 others.

In this episode, Conrad Stoll joins us once again to talk about how to use WCSession to pass data back & forth between our watchOS app and our iOS app. We'll use this power for the ultimate good, of course, by ordering a beer straight from our watch.

This episode was authored by Conrad Stoll.

Episode Links

Using WCSession to Send Data

import WatchConnectivity

let session = WCSession.default()
session.delegate = self

if session.activationState != .activated {
    session.activate()
    return
}

var context = session.applicationContext
context["beers"] = arrayOfBeerDictionaries()

do {
    try session.updateApplicationContext(context)
} catch let error {
    print(error)
}

Monitoring Session Activation State

public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
    if activationState == .activated {
        // Send Data If Necessary
    }
}

Using WCSessionDelegate to Receive Data

func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
    if let arrayOfDictionaries = applicationContext["key"] as? [[String : AnyObject]] {

    }
}

Handling Background Delivery on watchOS App

func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
    for task in backgroundTasks {
        if let watchConnectivityBackgroundTask = task as? WKWatchConnectivityRefreshBackgroundTask {
            handleWatchConnectivityBackgroundTask(watchConnectivityBackgroundTask)
        } else {
            handleRefreshTask(task)
        }
    }

    completeAllTasksIfReady()
}

Handling Background WatchConnectivity Task Completion Correctly

var watchConnectivityBackgroundTasks: [WKWatchConnectivityRefreshBackgroundTask] = []

override init() {
    super.init()

    let session = WCSession.default()

    // https://developer.apple.com/library/content/samplecode/QuickSwitch/Listings/QuickSwitch_WatchKit_Extension_ExtensionDelegate_swift.html
    session.addObserver(self, forKeyPath: "activationState", options: [], context: nil)
    session.addObserver(self, forKeyPath: "hasContentPending", options: [], context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    DispatchQueue.main.async {
        self.completeAllTasksIfReady()
    }
}

func completeAllTasksIfReady() {
    let session = WCSession.default()
    // the session's properties only have valid values if the session is activated, so check that first
    if session.activationState == .activated && !session.hasContentPending {
        watchConnectivityBackgroundTasks.forEach { $0.setTaskCompleted() }
        watchConnectivityBackgroundTasks.removeAll()
    }
}

Schedule a Future Background Refresh

WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: Date(timeIntervalSinceNow: 60 * 5), userInfo: nil) { (error) in

}

Building a Watch App Request/Response System

let session = WCSession.default()
session.sendMessage(["message" : "refreshRequest"], replyHandler: { (items : [String : Any]) in
    if let array = items["beers"] as? [[String : AnyObject]] {
        self.handleArrayOfBeers(array: array)
    }
}) { (_) in

}

Handling Request and Sending Response on iPhone App

func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
    if let message = message["message"] as? String, message == "refreshRequest" {
        replyHandler(["beers" : arrayOfBeerDictionaries()])
    }
}

This episode uses Watchos 3, Xcode 8.2.