I have an existing iOS 9.2 and watchOS 2.1 app that uses sendMessage
and transferUserInfo
to send data from the iPhone to the Apple Watch. If sendMessage
fails, I am using transferUserInfo
to queue the data for later delivery:
// *** In the iOS app ***
self.session.sendMessage(message, replyHandler: nil) { (error) -> Void in
// If the message failed to send, queue it up for future transfer
self.session.transferUserInfo(message)
}
// *** In the watchOS app ***
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
// Handle message here
}
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
// Handle message here
}
Without changing any code and running the app on iOS 9.3 with watchOS 2.2 on a real device (simulator does not have the same problem), sendMessage
delivers data to the Apple Watch as long as the watch is within range and the screen is on. This is as expected and how it previously worked. However, if the screen is off and sendMessage
fails, transferUserInfo
no longer delivers data to the Apple Watch when the screen turns back on.
In an attempt to find where this was erroring out, I added the following WCSessionDelegate
method to see if the iOS app was failing to send the data:
func session(session: WCSession, didFinishUserInfoTransfer userInfoTransfer: WCSessionUserInfoTransfer, error: NSError?) {
// Called when self.session.transferUserInfo completes
}
This method does get invoked after transferUserInfo
is called, but there is no error returned and the iOS app seems to indicate that the transfer occurred successfully.
At first I thought that perhaps the time it takes to transfer the data has been increased, but after leaving the device alone for a day the data still had not transferred. I am now somewhat suspicious it has something to do with the new multi-watch API, and perhaps the iOS app needs to know a specific watch to send it to, though I only have ever had a single watch paired. Does anyone have any ideas on what might have changed and how to correctly use transferUserInfo
?
I think I have this working now. First, I had to add the new WCSessionDelegate methods to my iOS app:
@available(iOS 9.3, *)
func session(session: WCSession, activationDidCompleteWithState activationState: WCSessionActivationState, error: NSError?) {
if activationState == WCSessionActivationState.Activated {
NSLog("Activated")
}
if activationState == WCSessionActivationState.Inactive {
NSLog("Inactive")
}
if activationState == WCSessionActivationState.NotActivated {
NSLog("NotActivated")
}
}
func sessionDidBecomeInactive(session: WCSession) {
NSLog("sessionDidBecomeInactive")
}
func sessionDidDeactivate(session: WCSession) {
NSLog("sessionDidDeactivate")
// Begin the activation process for the new Apple Watch.
self.session.activateSession()
}
And similarly to my watchOS app:
@available(watchOSApplicationExtension 2.2, *)
func session(session: WCSession, activationDidCompleteWithState activationState: WCSessionActivationState, error: NSError?) {
if activationState == WCSessionActivationState.Activated {
NSLog("Activated")
}
if activationState == WCSessionActivationState.Inactive {
NSLog("Inactive")
}
if activationState == WCSessionActivationState.NotActivated {
NSLog("NotActivated")
}
}
But transferUserInfo was still not working for me, specifically when the Apple Watch's screen was off. Below is how I was sending information between an iPhone and Apple Watch in iOS 9.2/watchOS 2.1:
func tryWatchSendMessage(message: [String : AnyObject]) {
if self.session != nil && self.session.paired && self.session.watchAppInstalled {
self.session.sendMessage(message, replyHandler: nil) { (error) -> Void in
// If the message failed to send, queue it up for future transfer
self.session.transferUserInfo(message)
}
}
}
I had assumed that sending a message from the iPhone to the Apple Watch when the watch's screen was off was causing transferUserInfo to fail because it was in the error handler of sendMessage. sendMessage also worked as expected when the screen was on. However, it looks like sendMessage's error reply handler does not always get called if your watch's screen is off, even though the request fails. This is different behavior from the previous OS versions. This also seems to have caused a cascade effect where subsequent messages also failed even though conditions were appropriate. This is what made me believe that transferUserInfo was to blame.
I found that in order for my messages to go through reliably, I needed to check for both reachable and activationState. Since I also wanted to continue supporting earlier iOS and watchOS versions, my tryWatchSendMessage method became the following:
func tryWatchSendMessage(message: [String : AnyObject]) {
if #available(iOS 9.3, *) {
if self.session != nil && self.session.paired && self.session.watchAppInstalled && self.session.activationState == .Activated {
if self.session.reachable == true {
self.session.sendMessage(message, replyHandler: nil) { (error) -> Void in
// If the message failed to send, queue it up for future transfer
self.session.transferUserInfo(message)
}
} else {
self.session.transferUserInfo(message)
}
}
} else {
// Fallback on earlier versions
if self.session != nil && self.session.paired && self.session.watchAppInstalled {
if self.session.reachable == true {
self.session.sendMessage(message, replyHandler: nil) { (error) -> Void in
// If the message failed to send, queue it up for future transfer
self.session.transferUserInfo(message)
}
} else {
self.session.transferUserInfo(message)
}
}
}
}
Making these changes seem to have resolved the issues I was seeing. I am interested to see if these help resolve anyone else's issues, or if there are still related issues to transferUserInfo not working.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With