watchOS

watchOS 2: How to communicate between devices using Watch Connectivity

watchOS 2

If you haven’t heard by now, there’s been some great news – we finally can create native apps for the Apple Watch! Yay! As great as that is, it now means that there are some changes to how we communicate between devices for WatchOS 2. I’ll do a run through of the different types now, but be on the lookout for some updated WatchKit tutorials soon. Note that these are subject to change since WatchOS 2 is still in beta.

There’s a new framework called the Watch Connectivity framework that offers several ways of communicating between devices. Overall, I’m pretty excited about Watch Connectivity. It definitely gives you a lot more power, but you’ll still need to enable things like shared app groups for your apps if you want to use only one data store. While it’s great that the Watch extension now runs directly on the watch device, this means that now you have two storage points to worry about, instead of one. Check out my other post on best practices to share data for other tips like this.

Device communication has also gotten a little more complicated. Instead of having only one default way (openParentApplication), there are now a lot more options! Let’s see what these are.

Watch Connectivity – Sessions

Before we get to the communication portion, you’ll need to set your apps up to receive data by creating a Watch Connectivity session:

//Swift
if (WCSession.isSupported()) {
     let session = WCSession.defaultSession()
     session.delegate = self 
     session.activateSession()
 
}
//Objective-C
if ([WCSession isSupported]) {
      WCSession *session = [WCSession defaultSession];
      session.delegate = self;
      [session activateSession];
}

First, check if a session is supported. If it is, then we’ll set up a default session. Next, you’ll set the session’s delegate to be self – don’t forget to add WCSessionDelegate to your header if you’re using Objective-C. Lastly, you’ll activate the session and be able to receive incoming content.

As a best practice, you’ll also want to make sure that the session has a paired Apple Watch and that the watch app is installed on the watch too. That way, you won’t waste precious resources trying to send data when there’s nothing to send it to.

Communication Categories

There are two new categories of communication – background transfers and interactive messaging:

Background transfers

  • Best when information isn’t needed immediately
  • Operating system determines the most suitable time to send the data
  • Content is queued up for transfer
Interactive messaging

  • Best for information needed immediately
  • Requires reachable state

Background Transfers

Within background transfers, there are 3 different ways of communicating, depending on the data you want to transfer.

Application Context

The most interesting thing about using application context is that any information waiting in a transfer queue will get overridden by the latest data. Only the most up-to-date context is then passed over when the system determines the most opportune time.This is useful for things like a news app, where the user only wants the latest information anyway. Most watch apps will probably use this mode of communication since the user only needs the most up-to-date information on that small screen.

//Swift
do {
    let applicationDict = // Create a dict of application data
    try WCSession.defaultSession().updateApplicationContext(applicationDict)
} catch {
    // Handle errors here
}
//Objective-C
NSDictionary *applicationDict = // Create a dict of application data
[[WCSession defaultSession] updateApplicationContext:applicationDict error:nil];

To receive the file on the other side, use the delegate callback for this called session:didReceiveApplicationContext: to grab that application context that you just sent through.

User Info Transfer

User info transfer seems somewhat similar to openParentApplication of WatchOS 1. Unlike application context, all content is queued up for delivery in FIFO order, so nothing will be overridden. This is useful for cases where all data is needed by the receiving device, like user profile information for example.

//Swift
let applicationDict = // Create a dict of application data
let transfer = WCSession.defaultSession().transferUserInfo(applicationDict)
//Objective-C
NSDictionary *applicationDict = // Create a dict of application data
[[WCSession defaultSession] transferUserInfo:applicationDict];

On the opposite end, use session:didReceiveUserInfo: to receive the application dictionary you just sent.

File Transfer

File transfer is pretty straightforward. Use this when you want to transfer files between your devices. Here’s how you do it:

//Swift
let url = // URL of file
let data = // Create dictionary of data 
let fileTransfer = WCSession.defaultSession().transferFile(url,
metadata:data)
//Objective-C
NSURL *url = // URL of file
NSDictionary *metadataDict = // Create dictionary of data 
WCSessionFileTransfer *fileTransfer = [[WCSession defaultSession] transferFile:url metadata:metadataDict];

You can check on the queued file transfers waiting to be delivered by accessing the outstandingFileTransfers property on WCSession. To receive the file on the other side, use session:didReceiveFile:.

Interactive Messaging

This category also requires that your devices be in a reachable state. This differs based where you’re communicating from.

iOS app: The paired Apple Watch must be connected via Bluetooth and the watch app must be running in the foreground.
Watch app: The paired iPhone has to be connected via Bluetooth.

You can determine this via the reachable property.

//Swift
if (WCSession.defaultSession().reachable) {
    // Do something
}
//Objective-C
if ([[WCSession defaultSession] isReachable]) {
    // Do something
}

After you’ve determined that you’re in a reachable state, interactive messaging has two methods that you can call, depending on if you’re planning on sending a dictionary of information or data. These are sendMessage:replyHandler:errorHandler: and sendMessageData:replyHandler:errorHandler:.

//Swift
let applicationDict = // Create a dict of application data
WCSession.defaultSession().sendMessage(applicationDict,
       replyHandler: { ([String : AnyObject]) → Void in
          // Handle reply
       })
        errorHandler: { (NSError) → Void in
          // Handle error
});

let applicationData = // Create data
WCSession.defaultSession().sendMessageData(applicationData,
       replyHandler: { ([String : AnyObject]) → Void in
          // Handle reply
       })
        errorHandler: { (NSError) → Void in
          // Handle error
});

//Objective-C
NSDictionary *applicationDict = // Create a dict of application data
[[WCSession defaultSession] sendMessage:applicationDict
                           replyHandler:^(NSDictionary *replyHandler) {
            
                           }
                           errorHandler:^(NSError *error) {
            
                           }
];

NSData *applicationData = // Create data
[[WCSession defaultSession] sendMessageData:applicationData
                           replyHandler:^(NSDictionary *replyHandler) {
            
                           }
                           errorHandler:^(NSError *error) {
            
                           }
];

For the callbacks on these functions, check out session:didReceiveMessage: and session:didReceiveMessage:replyHandler:.


Thoughts on this new framework? I’d love to hear them. As always, please ask questions below and I’ll get back to you as soon I can.

67 thoughts on “watchOS 2: How to communicate between devices using Watch Connectivity

    1. Technically you don’t need to check for reachable state (you can just trigger sendMessage without the reachable check in your app). It’s just a best practice for out in the field. Not sure if it works without loading the app on your device at this point.

  1. Whenever I attempt to add the function in for the receiving side I get an error ” optional func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject])” could you fill out the fields of this function that correspond with your given example so I can see if I have made a coding error? Thanks!

    1. I’m not sure what the context of your error is John. I didn’t come across anything like that. Try out this sample code and see if it works (make sure you’ve also imported Watch Connectivity):

      import WatchConnectivity
      func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
              let info = userInfo["key"] as? String
              //do what you want here
      }
      

      Hope this helps. Let me know if this doesn’t work for you.

    1. I tested your example, do you know why the display of the Label is imediat on the Watch and it takes at least five seconds to display on the iPhone?
      I have the same problem on my APP

  2. Thanks for nice tutorial.

    I am wondering ‘sendMessage()’ will wake up app on the phone and bring it to the front or leave it run as background ?

    Really appreciate to your nice post 🙂

      1. Much appreciated to quick & kind reply.

        Could you please clarify ‘enable handoff’ ?

        Does it mean that I should do by manual or is there any way to achieve by programming ?

  3. Thank you for the tutorial, I was wondering if watch connectivity is what would be needed in order to do large network calls? Would you have the phone run the network call and then send the data back to the watch?

    1. To be more specific, would an app like Yik Yak use updateApplicationContext? Since it needs to get the users location from the phone and then make an http request

      1. Actually, since watch apps are now native with watchOS 2, you can make the HTTP request from the watch itself. However, if you still need to update the phone app too, then you will need to use one of the Watch Connectivity options to pass the data back to the phone.

  4. Hi Kristina. First thanks! I made a test project using your template. This works great. So I created my real project using the ” iOS App with WatchKit App” Xcode selection .

    Copied the code from the test project.

    I get “willActivate”, but “session didReceiveApplicationContext: applicationContext” does not fire.

    Am I missing something? I’ve checked info.plist in the three locations (iOS app, WatchKit app, and WatchKit extension..

    Thanks for any tips.

    1. I am trying to create a framework for WCSession for iOS side but unable to receive messages which are sent by Watch as “isReachable” is NO on iOS. Where did you initialize the session object?

  5. Hi Kristina – Great article and thanks.

    In watchOS 1 we could shared common source code and assets in a framework project as it all ran on the iPhone. With watch OS2 this is not possible to have a framework that supports both watch and iPhone platforms.

    With connectivity I’d suggest we’d still want to be sharing both this comms code and classes that interact with it.

    any suggestions on the bets approach for sharing common code and assets now ?

  6. On simulator(iPhone+iWatch) it works properly but on actual device (iOS 9.0 and iWatch 2.0) userInfo return as a nil.
    Currently I am using Xcode 7.0 beta 4 version.

    iOS app Code :
    if ([WCSession isSupported]) {
    _watchKitSession = [WCSession defaultSession];
    _watchKitSession.delegate = self;
    [_watchKitSession activateSession];
    }

    if(_watchKitSession.isPaired) {
    NSDictionary *applicationData = [[NSDictionary alloc] initWithObjects:@[@”SampleDat”] forKeys:@[@”sampleKey”]];
    [[WCSession defaultSession] transferUserInfo:applicationData];
    }

    Watch OS 2 Code :

    – (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary *)userInfo{

    }

    userInfo is received as a nil.

  7. Issue is resolved 🙂
    I just updated the iWatch OS version from watchOS 2.0 (13s5293f) to watchOS 2.0(13S5305d).

  8. Hi,
    Your example WatchOS2CounterObjC work.
    I modified your example and work… 🙂

    Now I create new projects with Xcode 7 beta 4… and don’t work… 🙁

    [[WCSession defaultSession] isReachable] –>NO

    and i receive this error:
    Error Domain=WCErrorDomain Code=7004 “The operation couldn’t be completed. (WCErrorDomain error 7004.)

    Have you some ideas?

    there is a link with the same problems:
    ùhttp://stackoverflow.com/questions/31583592/isreachable-is-false-when-sending-message-from-watch-app-to-ios-app

    Thanks.

  9. Running Beta 5 (Beta 4 had major problems.. ) However when I downloaded the source referenced above from Gi.. I get the following error when trying to compile:
    ==============
    required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder) // A non-failable initalizer cannot chain to failable intializer ‘init(coder:)’ written with ‘init?’

    commonInit()
    }
    =========
    This is in the viewcontroller.swift… is anyone else seeing this?

  10. btw, I was able to “fix the code” and get it to run.. Here’s the updated code:
    ==========
    required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)

    commonInit()
    }
    ==========

    1. Yes, you’ll have to enable handoff for your app. It’s not something you can do through Watch Connectivity. If you look at some apps like Yelp or Foursquare, when you use their watch app, you can also see the phone app icon in the bottom left corner of your lock screen which allows you to jump straight to the app when you swipe up on it (this is what handoff enables you to do). Check out the apple docs for more info: https://developer.apple.com/handoff/

    1. I would handle your transfers in the app delegate instead of the view controller if you have multiple sources sending information. When you create the dictionary for application context, make sure to include an identifier as one of the key/value pairs and switch on that identifier in your app delegate to deal with the content appropriately.

  11. Kristina, these tutorials are awesome! Thanks for taking the time to help make our efforts quicker :). Got my ApplicationContext code running great.

    I was wondering if you had tried the file transfer with a Core Data .sqlite yet? My app has a small store that use to be shared but now needs to be replicated. I don’t want to send the individual records overtime, just send the .sqlite file from the phone app to the watch after modifications. I would love to hear your thoughts on this, researching other sources now.

    Thanks again for you inspiration and help on these WatchOS topics!

    Steve

    1. Hey Steve, haven’t tried sending a SQLite file over myself yet, but it should be doable as long as you can pass the NSURL representation of the file since that’s what the file transfer API takes as a parameter. One word of caution is to make sure this file isn’t too large (the larger the file, the slower the transfer). Might end up bringing you back to watchOS 1 slowness.

  12. So I get issue in creating the session
    session.delegate = self; always give me a warning saying
    Assigning to ‘id<WCSessionDelegate _Nullable' from incompatible type 'ViewController *const_Strong'

    And trying to turn program with it seem to cause it to break at that point so anyone know whats up with that?

  13. Hi,

    Where do I actually put any of this stuff? I have an Objective-C app that I’ve added a watchOS2 app to (in Obj-C, too). There’s nothing in the GlanceController.m, InterfaceController.m, ExtensionDelegate.m etc. that say anything about sessions or delegates. I thought the standard boilerplate code would’ve included this sort of basic code?

    So, what code goes into ExtensionDelegate.h/m to setup the session/delegate?

    How do I send a dictionary (userInfo) to the Watch? Where do I setup the session/delegate in the iOS app?

    What code goes into the GlanceController.m to access the data the iOS app just sent? Same for InterfaceController.h/m.

    I’ve been searching the net for days trying to figure this out, and it seems that no one has an example app that I could look at and see where the session/delegate are setup? The examples are either all in Swift, or assume you can understand the vague documentation Apple have on their site.

    I’m sorry, I’m just getting very frustrated as nothing I try seems to work. Thanks for your help.

    1. Hey Chris, try checking out some of my sample apps here: https://github.com/kristinathai/WatchOS2WatchConnectivityDemos

      I have sample apps in objective c for both the send message and application context APIs. All of the session code has to be added by you. Not all watch apps have to have a watch connectivity session (as not all watch apps need to transfer data between devices), so it won’t be in the standard starting app code.

      You’ll need to set up the session either in your extension delegate (appDidFinishLaunching), or if you’re only using it within one interface controller, you can create it in the init function of that interface controller.

      I have another tutorial that illustrates this for application context: http://kristina.io/watchos-2-tutorial-using-application-context-to-transfer-data-watch-connectivity-2/

      Does this help?

      1. Hi. I *might* have set it up properly now, but this is mainly through trial and error. I can’t be doing anything different to how a million other developers need it to work…?

        Anyway, I’m now stuck on file transfers. I used to send an image across from the iOS app to the Watch extension via shared app groups, and as NSData in a dictionary. Can’t use that now, so I thought I’d try applicationContext – can’t use that as I get an error: “payload is too large”. So, let’s try file transfers, which now involves writing a whole lot of code around sending each image as a file, and having to store it somewhere on the Watch, and then more code to determine if the image is available on the Watch or not before trying to show it. (This was MUCH easier in watchOS1.)

        I just want to put an image in the background of a group on the Watch. NSData was easy. This file transfer malarkey is ridiculously bothersome.

        So, you say in the tutorial above that we need to use session:didReceiveFile:. I’ve implemented this, but the file is deleted if I don’t move it somewhere permanent. How do you suggest I do this? Where am I allowed to move the file? How do I figure out where it should go?

        I’m getting really frustrated with this. Apple have made this much more difficult than it needed to be. ALL of us developers now have to write code to handle missing files, files that haven’t yet transferred, files that need to be deleted when they’re no longer used on the Watch?

        I’ve searched the net for hours trying to find out how to move a file once it arrives on the Watch, and I’m stumped. This will likely be my last watchOS app for a long time.

  14. Hi Kristina. It almost helps but I’m still at a loss. I’m usually pretty good at this!

    I have only one view controller (MasterVC) in the iOS app that needs to send an NSDictionary to the Watch extension so I believe I set it up in there rather than AppDelegate? If so, what code actually goes in the .h file and in the .m file? I can create the WCSession if supported, fine, but it never connects to the Watch.

    InterfaceController on the Watch creates a table of the items from the dictionary. GlanceController also pulls data from that dictionary.

    I read that you can only have one delegate on each side, so it makes sense to have MasterVC do that in the iOS app, but where in the Watch extension?

    If I make ExtensionDelegate the delegate, how do both InterfaceController and GlanceController get at the dictionary? Or should I have InterfaceController as the delegate and somehow send the necessary data to GlanceController? (How?)

    I guess what I’m missing is actual code for each file. For example, some other site said to create a delegate object property in InterfaceController.h, but why?

    My Watch extension also needs to send a dictionary to MasterVC to action something following a force touch menu item press. At the moment, the AppDelegate receives that instruction and sends a notification to MasterVC to do the appropriate action. Would this now move into MasterVC?

    Also, I was using MMWormhole for the watchOS 1 version of the Watch extension, do I still keep all this code around if I want to include the watchOS 1 app? I guess the OS knows that the wormhole is setup between the old extension, and so will continue to work fine? (I’ll kill v1 in a future update.)

    Thanks.

  15. Hi Kristina, great post!

    In WatchOS1, openParentApplication used to launch the iOS app in the background. When a user launched the watch app, i’ve immediately presented the most recent data stored in my shared app group and called openParentApplication in the background to make sure I get the most updated information from the IOS app. Is there a way to achieve the same results in watch os2? It seems like I cannot use the shared app group and that the new API does not wake up the app, does it?

    Thanks,

    1. sendMessage should wake up the phone app in the background in order to receive the message from the watch (both apps have to be active in order to pass data, but not necessarily in the foreground). However, with watchOS 2 you’ll need to maintain two separate data stores now that the watch app is native. You can use Watch Connectivity to transfer data between the two stores though.

      1. But I found that sendMessage only works if iOS app counterpart is either in Background or Foreground. If iOS app is not running at all, sendMessage cannot awake the iOS App. Both replyHandler and errorHandler were not called if App is not running in background / foreground. I am not sure why?

  16. Hi Kristina,

    Great post! I am trying to create a dynamic framework with WatchConnectivity framework and I am calling WCSession activation in viewDidLoad only but isReachable on iPhone always show NO and hence is unable to receive messages from Watch though Watch shows YES for isReachable. Can you please tell me what can be wrong here? I have spent last three days 🙁

  17. Hi kristina,

    i am trying to check it in emulator but its showing data received is null.

    is there any way to check it in emulator or it can only be tested on real device.

    one more doubt is it necessary to run the app on both side phone and watch in order to communicate. What if the app is not running on iPhone and we try to establish a communication from apple watch.?

    Thx.
    Rohit

  18. Hi kristina,

    Thank you for your tutorial that is really great and really made me move forward.
    I would like to receive a 2 Controller Interface Context of the Watch and I do not see how

  19. Hi, great thread, thanks!

    Im struggling a bit figuring out how do I get the watch to ask the phone for data when not using sendmessage?

    The phone app has all the data in CoreData and that side is working a treat. Now I need to be able to get the watch to ask the phone (when the phone app is not in foreground) to send the latest data which can be in the application context.

    I understand how to send from the phone to the watch and from the watch to the phone but…how to I basically when I open the watch app get the watch to ask for an updated application context?

    Thanks

    1. You could just use sendMessage from the watch to send a message to the iOS app telling it that you want an updated context. Based on your sendMessage payload from the watch, you can switch on the data that you want to send back (if there are multiple scenarios). Unfortunately, it will be pretty slow since you’ll be waiting for 2 sendMessage trips to go through.

      1. Hi, thanks but did you not say that sendMessage is for when both apps are active? I’m talking about the watch app being open and the phone app closed in the users pocket…

        1. In fact, I just got it to work like this…

          func requestFromPhone() {

          if WCSession.isSupported() {

          do {

          let dictionary = [“requestData”: NSNumber(bool: true)]

          try WCSession.defaultSession().updateApplicationContext(dictionary)

          } catch {

          // add error handler here…
          }
          }
          }

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

          if let array = applicationContext[“news”] as? NSDictionary {

          print(“And the number of records is: \(array.count)”)

          }
          }

          and in the appdelegate on the phone I have this as the listener for watch requests:

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

          if let request = applicationContext[“requestData”] as? Bool {

          let session = WCSession.defaultSession()

          if request && session.watchAppInstalled {

          // process data and send to watch
          }
          }
          }

          1. Cool! I’d be curious to see what performance is like on the actual device. I was under the impression that background transfers wouldn’t be as quick as interactive messaging, but glad to know it works 🙂

          2. Yes, I have noticed that the transfer is very much delayed (well in the simulator) hence as you say it is ok to use sendmessage when phone app is in background I will go for that 😉

        2. The requirements are different for sendMessage depending on which device you’re trying to communicate from according to the documentation. For the watch, all that needs to happen is that the paired iPhone has to be connected via Bluetooth (not necessarily in the foreground). However, if you’re communicating from the iPhone app, then yes, the watch and iPhone apps must both be in the foreground.

          1. Hi, am a bit confused as from reading about messaging I see stuff like this:

            “Before implementing interactive messaging, consider how likely your iPhone app and Watch app are to be active at the same time. Given the short lifespan of Watch apps, and the likelihood that they’re used while the user’s phone is tucked away in her pocket, your apps may get little opportunity to make use of interactive messaging.”

            So I still am unsure say I have a news app on phone and watch and the phone is in my pocket with the app not even running. I start the app on the watch which calls to the phone for the latest news ….. surely this situation falls outside interactive messaging using the statement above?

            So thats why I went for the background approach, but that seems to have delays so am at a total er what the hell should i do fork in the road?

  20. When would you use transfer files ? I am currently loading images for the watch app the iOS app already has.

    So far I do this via interactive messaging when would using files be better ?

    1. Technically, you’re not supposed to send images through interactive messaging. It’s supposed to be used just for small, realtime messages and you can come across a “Payload is too large” error (see this thread on the Apple Dev forums). transferFile allows you to send larger content in the background, but there is a tradeoff since it won’t be as immediate as interactive messaging.

  21. Thank you so much for the information! I am new to WatchOS and I am using the code for Swift interactive messaging and it tells me I need a semicolon between [String : AnyObject] and -> void and it says Consecutive statements on a line must be seperated by a ;. It also says expected expression. I can’t seem to figure out what I did wrong. Any help is much appreciated!

  22. Hi Kristina,

    You blogs are really helpful.
    One of the issues I am facing is when iphone is locked and i send message from watch, it wakes up the application on the phone but sql data as well as properties data is not accessible to iphone app that time.
    Just checking if you faced this issue.

  23. Hey kristina

    I just stuck with one problem that suppose i have installed app on iPhone. After that i have paired my apple watch with iPhone.My iPhone application stays in BACKGROUND . Now i launch my app in recently paired apple watch. but it does not sync my data from iPhone to apple watch.

    Look forward to hearing from you.

    1. Its hard to tell exactly what’s going on from the information you’ve provided. I’m assuming you don’t have anything set up from your watch app to pull down the latest information from your phone. One way you can do this is to use watch connectivity and call either
      updateApplicationContext or sendMessage to notify your phone app that you need data from it. Then return the information you need in the reply handler and update the watch app accordingly.

  24. Hi Kristina,

    Thank you for wonderful tutorial. I am having one scenario which I am not able to handle, Could you please help me to solve.

    How to share data from watch to iPhone when we reconnect the device using bluetooth without any action?

  25. Hey Kristina,

    I stumbled upon your tutorial which really helped springboard me on my watch app project. I have got things working pretty well. However, I’ve come up against something that seems so basic to me that I can’t figure out. When I run the watch app in the simulator, I want to simulate the watch “going to sleep” like it does on your wrist. I wasn’t able to figure out how to do that so I simply used the Hardware – Home option from the simulator. However, when I then try to tap on my watch app icon again I lost connectivity with the iOS app. I want to be able to simulate this process so that I can test my watch app’s applicationDidBecomeActive() code multiple times without having to launch everything again from Xcode. Am I missing something really basic here? Thanks for your help in advance.

  26. Hello, I am working on apple watch OS2. I want to send data from watch to Apple App when apple app is killed. How can I achieve it ?

Leave a Reply