6 Replies
      Latest reply on Jan 21, 2020 11:41 AM by cantchangenamewhyapplewhy
      huyFMR Level 1 Level 1 (0 points)

        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

        • Re: WatchConnectivity transferUserInfo stop working from Xcode 11
          Claude31 Level 8 Level 8 (7,895 points)

          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.

            • Re: WatchConnectivity transferUserInfo stop working from Xcode 11
              huyFMR Level 1 Level 1 (0 points)

              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

                • Re: WatchConnectivity transferUserInfo stop working from Xcode 11
                  Claude31 Level 8 Level 8 (7,895 points)

                  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.

                  • iOS 9.0+
                  • watchOS 2.0+
                  • Mac Catalyst 13.0+
                  • 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.

                  • iOS 9.0+
                  • watchOS 2.0+
                  • Mac Catalyst 13.0+
                  • 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.

                    • Re: WatchConnectivity transferUserInfo stop working from Xcode 11
                      huyFMR Level 1 Level 1 (0 points)

                      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

                • Re: WatchConnectivity transferUserInfo stop working from Xcode 11
                  cantchangenamewhyapplewhy Level 1 Level 1 (0 points)

                  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`.