watchOS

WatchKit Tutorial: Send data to parent iOS app

WatchKit beta 2 was just released as a part of the iOS 8.2 developer seed. One of the best new additions to the SDK is the ability to communicate back to the iOS parent app from the WatchKit extension. This allows us to essentially “close the loop” and pass information from the watch to the watch extension and to the iOS parent app that supports it.

I created a simple counter app for the watch which saves its count values back to a simple iOS table view app when the user hits a Save button. This tutorial will demonstrate how to transfer data to the parent iOS app from the watch extension. See the finished project on GitHub.

watchkitdemo

Now before you jump into this tutorial, I highly recommend that you read Natasha’s Hello World WatchKit tutorial first. This tutorial will assume that you’re already a little bit familiar with WatchKit (and iOS development in general).

Create the Project

First create a new single view application iPhone project and call it “WatchKitCounterDemo.” For the purposes of this tutorial, I’ll be using Objective-C.

From here, you’ll create a new target for your WatchKit App under iOS -> Apple Watch -> WatchKit App. It will pre-fill all of the information for you based on your iPhone project file. Click Finish and you should now see a couple of new folders on the sidebar called “WatchKitCounterDemo WatchKit Extension” and “WatchKitCounterDemo WatchKit App.”

WatchKit App Creation

Create the Watch App

Let’s start with the WatchKit app. Open the WatchKitCounterDemo WatchKitApp folder and click on the Interface.storyboard file. You should see a blank watch storyboard interface that you can drag UI controls onto. Make your main interface look like the image below. You should use a WKInterfaceLabel and three WKInterfaceButtons.

WatchKit App Storyboard File

If you run the app now, making sure to select the WatchKitCounterApp Watch App scheme, you should be able to see this interface on the simulator (if you can’t see the watch display, in the iOS Simulator go to Hardware -> External Displays -> Apple Watch – 38mm or Apple Watch – 42mm).

That’s all there really is to the watch app. The only thing that the watch app has in it are storyboard and resource files. All the watch-related code is executed through the watch extension.

Once you’ve got that up and running, it’s time to connect actions to three watch buttons.

Create the Watch Extension

In your InterfaceController.m file, add the following code:

Counter property at the top

 @property (nonatomic, assign) int counter;

and IBActions (and a helper method) for each one of the buttons in the body.

- (IBAction)incrementCounter {
    self.counter++;
    [self setCounterLabelText];
}
- (IBAction)saveCounter {
   
}
- (IBAction)clearCounter {
    self.counter = 0;
    [self setCounterLabelText];
}
- (void)setCounterLabelText {
    [self.counterLabel setText:[NSString stringWithFormat:@"%d", self.counter]];
}

Now, from your Interface.storyboard file, connect the buttons to their appropriate actions in the InterfaceController.m file:

  • Hit -> incrementCounter
  • Clear -> clearCounter
  • Save -> saveCounter

You will also want to create an outlet for the WKInterfaceLabel called counterLabel and connect it back to the counter label on the watch storyboard.

@property (weak, nonatomic) IBOutlet WKInterfaceLabel *counterLabel;

Let’s look at the saveCounter method at this point. Here’s what you’ll need to fill it:

- (IBAction)saveCounter {
    //Send count to parent application
    NSString *counterString = [NSString stringWithFormat:@"%d", self.counter];
    NSDictionary *applicationData = [[NSDictionary alloc] initWithObjects:@[counterString] forKeys:@[@"counterValue"]];
    
    //Handle reciever in app delegate of parent app
    [WKInterfaceController openParentApplication:applicationData reply:^(NSDictionary *replyInfo, NSError *error) {
        NSLog(@"%@ %@",replyInfo, error);
    }];
}

Here’s where the awesome part is. To pass information to the parent iOS app, it just takes a couple lines of code – the openParentApplication:reply method. This takes an NSDictionary filled with any information that you need to send to the parent app. In this instance, I’m just sending an NSString counter value inside my applicationData dictionary, but you could obviously send more complex data.

When the openParentApplication method in the watch extension is called, this causes iOS to open the parent app and call the application:handleWatchKitExtensionRequest:reply: method to receive the dictionary from the watch extension. I’ll go over how to handle this at the end.

Notice the reply parameter in openParentApplication? We can use this to receive a message back from handleWatchKitExtensionRequest. You’ll see an example of this towards the end.

Read more about both of these methods in the Apple documentation.

That’s it for the extension. Now onto our main parent app.

Create the parent iOS app

Honestly, at this point, I would encourage you to download the GitHub source code and copy the WatchKitCounterDemo files into your project so you don’t have to waste time creating the table view if you’re just interested in the watch portion. You can skip to the next section to see how we handle getting the watch extension data.

However, if you want to brush up on some table view creation basics, here you go.

Let’s start with the easy and familiar part – creating the table view that will display our counter values. Open the Main.storyboard file and first embed the default view controller in a NavigationController. This will add a top nav bar. See the screenshot below for a nifty way to do this in the menu through Editor -> Embed In -> Navigation Controller (make sure to have the ViewController selected).

Embed in Navigation Controller

Now, drag in a table view from the right sidebar into the view controller and give the table view 1 prototype cell. Then click on the prototype cell you just created and give it a reuse identifier called “CounterCell.” You can do both of these actions in the Attributes Inspector (the one icon in the right sidebar that looks like a bookmark).

Prototype cells

Screen Shot 2014-12-11 at 3.13.23 PM

Your storyboard should look like this in the end:

Table View

One last thing in the Main.storyboard file – make sure you make the ViewController the TableView’s datasource. That way we can actually load the counter values into the table.

Let’s go on to the ViewController.h file now. Here’s what it should look like:

@interface ViewController : UIViewController <UITableViewDataSource>

@property (strong, nonatomic) NSMutableArray *counterData;
@property (weak, nonatomic) IBOutlet UITableView *mainTableView;

@end

Open up a split screen view on Xcode with Main.storyboard and ViewController.h and drag in an outlet for the table view called “mainTableView” into the view controller header file. You’ll also want to add in a NSMutableArray called “counterData” and “<UITableViewDataSource>” to the ViewController definition.

Almost done with the parent app…now let’s open up ViewController.m. Here’s what that should look like:

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.mainTableView reloadData];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.counterData.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellIdentifier = @"CounterCell";
    UITableViewCell *cell = [self.mainTableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
    
    NSString *counterValueString = [self.counterData objectAtIndex:indexPath.row];
    cell.textLabel.text = counterValueString;
    
    return cell;
}

@end

You can just copy and paste this straight. Now onto the last part – handling the dictionary we send from the watch extension.

 Handle the WatchKit extension data

Last but certainly not least is the AppDelegate.m file in the parent iOS app. We’ll now receive the dictionary passed to us from the WatchKit extension.

Create a property at the top:

@property (strong, nonatomic) NSMutableArray *tempCounterData;

and add this method:

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply {
    
    NSString *counterValue = [userInfo objectForKey:@"counterValue"];
    
    reply(@{@"insert counter value":counterValue});
    
    if (!self.tempCounterData) {
        self.tempCounterData = [[NSMutableArray alloc] init];
    }
    
    [self.tempCounterData addObject:counterValue];
    
    //Add the new counter value and reload the table view
    AppDelegate *tmpDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    ViewController *vc = (ViewController *)((UINavigationController*)tmpDelegate.window.rootViewController).visibleViewController;
    
    vc.counterData = self.tempCounterData;
    [vc.mainTableView reloadData];

}

Here you can see we’re getting the counterValue out of the userInfo dictionary passed to us from the extension. In order to keep track of all the counter values, I created a temporary counter data array to hold all the values as we get them. Lastly, we grab the view controller instance and load the counter array into the mainTableView.

One interesting thing that I came across while testing this was that you couldn’t print anything to the console, or try to debug in the area with a breakpoint. In fact, the only way I was able to tell that this method was even being called was by using reply to send back a message to the watch extension.

reply(@{@"insert counter value":counterValue});

You can see this is the second line I have in my handleWatchKitExtensionRequest method, mostly as a sanity check. You’ll be able to use this as a way to debug too.

That’s it! Run your program and it should look like this:

Finished Demo App

Every time you hit save, it should add the counter value to the table view.  You can check the console log to see the reply from handleWatchKitExtensionRequest too! Pretty cool huh?


Questions? Reply in the comments below and I’ll look into it.


See Part two of this tutorial here.

20 thoughts on “WatchKit Tutorial: Send data to parent iOS app

    1. I haven’t seen a way to push data straight from an iOS app to the WatchKit extension yet, although to be honest I haven’t done much research at this point. WatchKit is still in beta so this is probably a coming feature. However, there is a way to “respond” from your iOS app after the watch sends you data. In the handleWatchKitExtensionRequest method, notice how I use the

      reply(@{@"insert counter value":@(1)});
      

      to send back a simple message? You can actually include any code you want in this reply block and WatchKit will execute it. This can be seen in the Apple documentation:

      - (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply
      

      The app delegate receives the dictionary you pass into the userInfo parameter and uses it to process whatever request you made. If it provides a reply, WatchKit executes the block you provided in the reply parameter of this method.

      https://developer.apple.com/library/prerelease/ios/documentation/WatchKit/Reference/WKInterfaceController_class/index.html#//apple_ref/doc/uid/TP40014957-CH1-SW70

  1. Hello Kristina,

    I cloned this project on github , it passes the data to the app , but it does not open the app in iphone . My xcode is 6.2 . Already tried other projects but still no open the application by Apple Watch. You can a help me?

    Thank`s.

    1. Hi Andre, this was actually a bug in the beta version. In the official version, any interaction with the watch app will NOT open the parent app on the phone. The parent app is just updated in the background and will only open when the user opens it. I noted this bug in the continuation of this tutorial here: http://kristina.io/watchkit-tutorial-communicate-from-parent-app-back-to-watch-via-reply/

      One thing to note here, in the original tutorial written during iOS 8.2 beta 2, the iOS app launched when the user hit Save. This was apparently a bug and the iOS parent app will NOT launch anymore, but it will update in the background. You can click on the WatchKitCounterDemo app icon to launch the parent app and it will show all of your saved values. If the app is in the foreground, you can see the values updating like normal.

  2. The demo video you present suggest that hitting the Save button on the watch opens the app on the phone. Is that true? Is that possible?

    1. No, this was a bug in the beta 2 version. In the later releases of watchkit, this bug was fixed and the parent app no longer launches when the user interacts with the watch app. Please refer to my comment to Andre for the explanation.

      1. Ok. Thanks for the reply. I wish it did open the app, but then what if your phone is say locked in your pocket?
        Anyways, I hope they will figure it out and bring this “bug” back later on!

  3. Outstanding tutorial on the basics; got this up and running with minimal effort!

    I did run into a bit of a snag, though, creating the parent app from scratch: You didn’t mention (that I can find) actually connecting the data source/delegate methods from the ViewController class to the Storyboard view controller. So no matter how many times I tapped “Save”, it never updated the UITableView in the parent app.

    After making the connections in IB, this resolved the problems and showed everything just fine.

    Again, awesome tutorial. Thank you!

    1. i went through apple documentation…so is application data itself is userInfo dictionary? & if so why cant we call application data dictionary in app delegate?

      1. The applicationData dictionary is being passed to openParentApplication: as a parameter. Within that function in the app delegate, you’ll want to use userInfo as the dictionary name.

Leave a Reply