Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WCSession's transferUserInfo no longer reliably working in watchOS 2.2 with iOS 9.3

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?

like image 854
lehn0058 Avatar asked Mar 29 '16 13:03

lehn0058


1 Answers

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.

like image 100
lehn0058 Avatar answered Nov 10 '22 04:11

lehn0058