WatchConnectivity transferUserInfo stop working from Xcode 11

I have a dependent watch app that use to work fine in simulation for Xcode 10.2.1, but when I update to Xcode 11.x.x, it seem like the transfer data does not work anymore.


In Xcode 10.x.x the target for WatchKit App always trigger both iOS and Watch App. But since Xcode 11, it only triggers the Apple Watch simulator, so I have to run both targets, for WatchKit first and then run main app . I already double-check to use corrected pair simulators (corrected paired iPhone + Apple Watch simulators). Already checked all the

WCSesssionActivationState
to be activated,
WCSession.default.isReachable
to be true,
session didFinish userInfoTransfer
get called for the same target, but in the other target
session didReceiveUserInfo
does not get called at all.

Is there any configuration I need to do in addition?


==================================

This is the code in main App ViewController

==================================

import UIKit

import WatchConnectivity


class ViewController: UIViewController, WCSessionDelegate {


@IBOutlet weak var textFieldMessage : UITextField!

@IBOutlet weak var buttonSend : UIButton!

var wcSession : WCSession!


override func viewDidLoad() {

super.viewDidLoad()

// Do any additional setup after loading the view, typically from a nib.


wcSession = WCSession.default

wcSession.delegate = self

wcSession.activate()

}


//MARK: - Button Actions


@IBAction func clickSendMessage(_ sender : UIButton) {


let message = ["message" : textFieldMessage.text!]

do {

try wcSession.updateApplicationContext(message)

if wcSession.activationState == .activated {

if wcSession.isReachable {

let data = ["text": "User info from the iphone"]

wcSession.transferUserInfo(data)

}

}

} catch {

print("Something went wrong")

}

}


// MARK: - WCSessionDelegate


func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {

NSLog("%@", "activationDidCompleteWith activationState:\(activationState) error:\(String(describing: error))")

}


func sessionDidBecomeInactive(_ session: WCSession) {

print("%@", "sessionDidBecomeInactive: \(session)")

}


func sessionDidDeactivate(_ session: WCSession) {

print("%@", "sessionDidDeactivate: \(session)")

}


func sessionWatchStateDidChange(_ session: WCSession) {

print("%@", "sessionWatchStateDidChange: \(session)")

}


func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) {

DispatchQueue.main.async {

if session.isReachable {

print("Transfered data")

}

}

}

}


==================================

And InterfaceController in WatchKit Extension

==================================


import WatchKit

import Foundation

import WatchConnectivity


class InterfaceController: WKInterfaceController, WCSessionDelegate {


var session : WCSession?

@IBOutlet weak var sessionLabel : WKInterfaceLabel!


override func willActivate() {

// This method is called when watch view controller is about to be visible to user

super.willActivate()


session = WCSession.default

session?.delegate = self

session?.activate()

}


// MARK: - WCSessionDelegate


func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {

NSLog("%@", "activationDidCompleteWith activationState:\(activationState) error:\(String(describing: error))")

}


func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {

NSLog("didReceiveApplicationContext : %@", applicationContext)

sessionLabel.setText(applicationContext["message"] as? String)

}


func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {

print("9. InterfaceController: ", "didReceiveUserInfo")

DispatchQueue.main.async {

if let text = userInfo["text"] as? String {

print(text)

}

}

}

}



It is weird that the

wcSession.updateApplicationContext(message)
works fine but the
wcSession.transferUserInfo(data)
does not send data to apple watch, even the code went inside
print("Transfered data")
for ViewController

Replies

In simulator, did you launch both watch app and iOS App ?


- select Watch target in XCode

- Run

- Open both iOS and Watch app in simulator

Connection should be made.


I just tested with XCode 11.3.

Thanks for your response Claude31,


Did you use the method transferUserInfo from iOS to AppWatch ?


I did run both targets for watch app and then for iOS App. And from iOS app (ViewController) and I can still send message to WatchApp (InterfaceController) with this wcSession.updateApplicationContext(message), but the other method wcSession.transferUserInfo(data) does not worked. This is just a testing prototype, in my main app we use heavily data on transferUserInfo so I need to figure out the issue with breaking transferUserInfo

No, I don't use transferUserInfo.


To transfer from iOS to Watch, I use sendMessage.


func sendWatchTest() {
   
    // send a message to the watch if it's reachable
    if (WCSession.default.isReachable) {
        let message = ["TestID": true]
        WCSession.default.sendMessage(message, replyHandler: nil)
    }
}


Could you test this approach, in parallel to yours and see what you get ?

----------------------------


Doc show they are pretty similar, transfer guarantees delivery (with sendMessage, you have to handle in app):


sendMessage(_:replyHandler:errorHandler:)


Sends a message immediately to the paired and active device and optionally handles a response.

SDKs

  • iOS 9.0+
  • watchOS 2.0+
  • Mac Catalyst 13.0+
Framework

WatchConnectivity

Declaration

func sendMessage(_ message: [String : Any], replyHandler: (([String : Any]) -> Void)?, errorHandler: ((Error) -> Void)? = nil)


Discussion

Use this message to send a dictionary of data to the counterpart as soon as possible. Messages are queued serially and delivered in the order in which you sent them. Delivery of the messages happens asynchronously, so this method returns immediately.

If you specify a reply handler block, your handler block is executed asynchronously on a background thread. The block is executed serially with respect to other incoming delegate messages.

Calling this method from your WatchKit extension while it is active and running wakes up the corresponding iOS app in the background and makes it reachable. Calling this method from your iOS app does not wake up the corresponding WatchKit extension. If you call this method and the counterpart is unreachable (or becomes unreachable before the message is delivered), the

errorHandler
block is executed with an appropriate error. The
errorHandler
block may also be called if the
message
parameter contains non property list data types.

This method can only be called while the session is active—that is, the

activationState
property is set to
WCSessionActivationState.activated
. Calling this method for an inactive or deactivated session is a programmer error.



transferUserInfo(_:)


Sends the specified data dictionary to the counterpart.

SDKs
  • iOS 9.0+
  • watchOS 2.0+
  • Mac Catalyst 13.0+
Framework

WatchConnectivity


Declaration

func transferUserInfo(_ userInfo: [String : Any] = [:]) -> WCSessionUserInfoTransfer

Discussion

Call this method when you want to send a dictionary of data to the counterpart and ensure that it is delivered. Dictionaries sent using this method are queued on the other device and delivered in the order in which they were sent. After a transfer begins, the transfer operation continues even if the app is suspended.

This method can only be called while the session is active—that is, the

activationState
property is set to
WCSessionActivationState.activated
. Calling this method for an inactive or deactivated session is a programmer error.

Yes, I try adding the sendMessage at the same place, and it works, that strange, same code run in Xcode 10 works in both cases.


if wcSession.isReachable {

let data = ["text": "User info from the iphone"]

wcSession.transferUserInfo(data) // --> WatchKit does not receive this

let message = ["TestID": "send Message from the iphone"]

wcSession.sendMessage(message, replyHandler: nil) // --> WatchKit receive this

}

Look like I need to migrate to use sendMessage instead since we cannot deploy with Xcode 10 from April 2020

Did you figure out why transferUserInfo woun´t work? I've experienced a very similar problem where transferUserInfo and UpdateApplicationContext work totally fine on real devices. On Simulator only UpdateApplicationContext is doing his job whereas transferUserInfo is not.

I have noticed the same defect. WCSession's `transferUserInfo` is broken between Watch simulator and Phone simulator in Xcode 11.3 (perhaps all of Xcode 11 versions so far). No mention of it in any of the Xcode 11 release notes. Whereas `sendMessage` works fine. The exact same code works on real device but silently fails on simulator. This code worked on simulator just fine in Xcode 10.


This defect is shameful for the Simualtor team. They should have system tests that exercise `transferUserInfo` as well as `sendMessage`.

I've noticed this issue as well. It only affects the watchOS 6 simulator though. I currently use Xcode 11.4 together with the watchOS 5.3 simulator to test UserInfo transfers.

This helped me, but I had to put my phone simulator back to 12.4 to get bi-directional communication, even with Apple's own code at: https://developer.apple.com/documentation/watchconnectivity/using_watch_connectivity_to_communicate_between_your_apple_watch_app_and_iphone_app


This was specific to file transfers using WatchConnectivity. I filed a radar for this, expecting it to be a duplicate.

This is still happening for me in 2022 on WatchOS 9. It works on a physical device but not any of the simulators. I had to add this to my WCSessionDelegate on the apple watch controller

func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
   
}

In case this still matters, the watchOS Simulator doesn’t support transferFile(_:metadata:), transferUserInfo(_:), and transferCurrentComplicationUserInfo(_:). Please always use physical devices to test Watch Connectivity data transfers. This is documented in the API reference.