11

Lately, I am working on a project is related to Watch/iPhone communication again. But my code works sometimes and doesn’t work sometimes which is kind of weird to me because I think the code should either work or not. It cannot be 50/50. Therefore, I have no idea what goes wrong.

setup WCSession on iPhone:

class WatchCommunicationController: NSObject, WCSessionDelegate {

    var session : WCSession?

    override init(){

        //  super class init
        super.init()

        //  if WCSession is supported
        if WCSession.isSupported() {    //  it is supported

            //  get default session
            session = WCSession.defaultSession()

            //  set delegate
            session!.delegate = self

            //  activate session
            session!.activateSession()

        } else {

            print("iPhone does not support WCSession")
        }
    }

    ... ...
}

similar WCSession setup on Watch:

class PhoneCommunicationController: NSObject, WCSessionDelegate {

    var session : WCSession?

    override init(){

        //  super class init
        super.init()

        //  if WCSession is supported
        if WCSession.isSupported() {    //  it is supported

            //  get default session
            session = WCSession.defaultSession()

            //  set delegate
            session!.delegate = self

            //  activate session
            session!.activateSession()
        } else {

            print("Watch does not support WCSession")
        }
    }

    ... ...
}

send out message on Watch:

func sendGesture(gesture : GKGesture){

//  if WCSession is reachable
if session!.reachable {     //  it is reachable

    //  create the interactive message with gesture
    let message : [String : AnyObject]
    message = [
                "Type":"Gesture",
                "Content":gesture.rawValue
              ]

    //  send message
    session!.sendMessage(message, replyHandler: nil, errorHandler: nil)
    print("Watch send gesture \(gesture)")

} else{                     //  it is not reachable

    print("WCSession is not reachable")
}

}

related enum:

enum GKGesture: Int {
    case Push = 0, Left, Right, Up, Down
}

receive message on iPhone:

func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {

        //retrieve info
        let type = message["Type"] as! String
        let content = message["Content"]

        switch type {

        case "Gesture":
            handleGesture(GKGesture(rawValue: content as! Int)!)
        default:
            print("Received message \(message) is invalid with type of \(type)")
        }

    }

    func handleGesture(gesture : GKGesture){

        print("iPhone receives gesture \(gesture)")

        var notificationName = ""

        switch gesture {

        case .Up:
            notificationName = "GestureUp"
        case .Down:
            notificationName = "GestureDown"
        case .Left:
            notificationName = "GestureLeft"
        case .Right:
            notificationName = "GestureRight"
        case .Push:
            notificationName = "GesturePush"
        }

        NSNotificationCenter.defaultCenter().postNotificationName(notificationName, object: nil)

    }

somehow I can’t debug my Watch app on Xcode, the debug session just won’t attach. I don’t know why. Therefore, I debug one-sided with just the iPhone.

sometimes I got "receives gesture” print out, and sometimes not. And the same for getting the notification.

Jay Hu
  • 6,041
  • 3
  • 22
  • 33
  • Have you tried "Reset content and settings" and restarted your watch/iPhone simulators and Xcode? This is tips to get your debug session attached. Tell me how that goes and then I can try and help you with the WatchConnectivity issues – Philip Jul 08 '15 at 10:40
  • I debug with my iPhone and Apple Watch. – Jay Hu Jul 08 '15 at 15:46
  • did you have to do anything special for reachability? My code is petty much identical, but my check always returns `false` when checking reachability – prawn Jul 09 '15 at 18:05
  • I don't think so. I think as long as the iPhone is wifi connected, bluetooth opened, and pair with the apple watch. They should be reachable. – Jay Hu Jul 10 '15 at 06:00

3 Answers3

6

I don't know if Int would be wrapped around to NSNumber while being transfer within WCSession. If it would be, then that must be why when I use Int as the base class of the enum it won't work and works when String is the base class.

Connectivity Known Issue Your app may crash when using NSNumber and NSDate objects with the WCSession API.

Workaround: Convert an NSNumber or NSDate object to a string before calling WCSession APIs. Do the opposite conversion on the receiving side.

Watch OS 2 Beta 4 release note

sharpBaga
  • 151
  • 3
2

My guess is your call to sendMessage is returning an error in the cases where it fails, but you haven't implemented the error handler!! For now while you are getting up and running you can get away with just printing the error, but if this is shipping code you really ought to handle the appropriate errors:

//  send message
session.sendMessage(message, replyHandler: nil, errorHandler: { (error) -> Void in
    print("Watch send gesture \(gesture) failed with error \(error)")
})
print("Watch send gesture \(gesture)")
ccjensen
  • 4,578
  • 2
  • 23
  • 25
  • I fixed it by changing my enum from Int to String. And I works every time now. Could you think of why? More info: by the change every thing I press the button on watch to trigger this sendMessage, it shows tiny progressIndicator on status bar. But not anymore after the change. – Jay Hu Jul 08 '15 at 16:02
  • no, that doesn't make any sense to me :) I'd probably need to look at the actual code and be able to run it myself to get any more answers for you – ccjensen Jul 08 '15 at 16:14
1

Your flow is correct but the difficulty is to understand how to debug:

Debug Watch:

  1. Run the iPhone target and when it is done hit the Stop button.
  2. Open the iOS app inside the simulator (run it manually from the simulator and not from Xcode) and let it hang there.
  3. Switch to the Watch target (yourAppName WatchKit App), put the relevant breakpoint and run it.
  4. The iOS app will be put automatically in the background and then you will be able to use sendMessage method (at the Watch target) to send whatever you need and if you have a replayHandler in your iOS app you will even receive the relevant messages inside the sendMessage at your Watch target (i.e InterfaceController)

Small Swift example:

Sending a Dictionary from Watch to iOS app:

    if WCSession.defaultSession().reachable == true {
        let requestValues = ["Send" : "From iWatch to iPhone"]
        let session = WCSession.defaultSession()

        session.sendMessage(requestValues,
            replyHandler: { (replayDic: [String : AnyObject]) -> Void in
                print(replayDic["Send"])

            }, errorHandler: { (error: NSError) -> Void in
                print(error.description)
        })
    }
    else
    {
        print("WCSession isn't reachable from iWatch to iPhone")
    }

Receiving the message from the Watch and sending a replay from the iOS app:

    func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {

      print(message.values)

      var replyValues = Dictionary<String, AnyObject>()

      replyValues["Send"] = "Received from iphone"
      // Using the block to send back a message to the Watch
      replyHandler(replyValues)
     }

Debug iPhone:

The exact opposite of debug watch

Also, the answer by @sharpBaga has an important consideration.

OhadM
  • 4,687
  • 1
  • 47
  • 57