I want my complication to get data from the iPhone via Watch Connectivity. I am using sendMessage
Instant Messaging technology.
I don't want my iPhone app to be open when I try to get data, so this needs to work in the background.
In my ViewController on my iPhone:
import UIKit
import WatchConnectivity
class ViewController: UIViewController, WCSessionDelegate {
var session: WCSession!
override func viewDidLoad() {
super.viewDidLoad()
if WCSession.isSupported() {
self.session = WCSession.defaultSession()
self.session.delegate = self
self.session.activateSession()
}
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
if message.count != 1 { return }
if message["request"] != nil {
replyHandler(["response" : "data"])
}
}
And in my ComplicationController
var session: WCSession!
func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {
if complication.family != .ModularSmall {
handler(nil)
}
if WCSession.isSupported() {
self.session = WCSession.defaultSession()
self.session.delegate = self
self.session.activateSession()
}
var respondedString = "not"
session.sendMessage(["request" : ""], replyHandler: {
(resp) -> Void in
respondedString = resp["response"]
}, errorHandler: nil)
let circularTemplate = CLKComplicationTemplateModularSmallSimpleText()
circularTemplate.textProvider = CLKSimpleTextProvider(text: respondedString)
let timelineEntry = CLKComplicationTimelineEntry(date: NSDate(), complicationTemplate: circularTemplate)
handler(timelineEntry)
}
The only thing I can see on my Watch is "not". Why doesn't the complication show the received data?
The main issue is that you're trying to make an asynchronous call within your complication controller.
The code following your sendMessage:
call will be executed before your reply handler has even gotten a response. This is why your complication shows "not" as the template's text has been set, before you have received a reply.
Sometime later, after getCurrentTimelineEntryForComplication
has returned, sendMessage
will receive a response and call the reply hander, which will merely set respondedString
, then exit that block.
What you should avoid doing:
You should consider Apple's recommendations, and not try to fetch any data within the complication controller.
The job of your data source class is to provide ClockKit with any requested data as quickly as possible. The implementations of your data source methods should be minimal. Do not use your data source methods to fetch data from the network, compute values, or do anything that might delay the delivery of that data. If you need to fetch or compute the data for your complication, do it in your iOS app or in other parts of your WatchKit extension, and cache the data in a place where your complication data source can access it. The only thing your data source methods should do is take the cached data and put it into the format that ClockKit requires.
Also, any activity you perform within your data source will needlessly use up the daily execution time budget that is allotted to your complication.
How can you provide data to your complication?
Apple provides a Watch Connectivity transferCurrentComplicationUserInfo
method which will immediately transfer (a dictionary of) complication info from the phone to the watch.
When your iOS app receives updated data intended for your complication, it can use the Watch Connectivity framework to update your complication right away. The transferCurrentComplicationUserInfo: method of WCSession sends a high priority message to your WatchKit extension, waking it up as needed to deliver the data. Upon receiving the data, extend or reload your timeline as needed to force ClockKit to request the new data from your data source.
On the watch side, you have your WCSessionDelegate
handle didReceiveUserInfo
and use the data you received to update your complication:
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let ... { // Retrieve values from dictionary
// Update complication
let complicationServer = CLKComplicationServer.sharedInstance()
guard let activeComplications = complicationServer.activeComplications else { // watchOS 2.2
return
}
for complication in activeComplications {
complicationServer.reloadTimelineForComplication(complication)
}
}
}
Apple engineers generally recommend setting up a data manager to hold the data. In your complication controller, you would retrieve the latest information from the data manager to use for your timeline.
There are several existing projects on GitHub which use this approach.
If you still prefer to request data from the watch side:
You'd want to move your WCSession
code out of the complication controller, into the watch extension, and activate it as part of the WKExtension
init.
The key is to have the reply handler manually update the complication once the data is received.
When your session delegate's reply handler is called, you can use the update complication code I supplied earlier to reload your complication's timeline.
If you use scheduled complication updates to trigger this, the downside to that particular approach is that you'll be performing two updates. The first update would initiate the request for data, but not have any new data to use. The second (manual) update happens after the data is received, and this is when the new data would appear on the timeline.
This is why the approach of supplying data in the background from the phone works better, as it only requires one update.