watchOS

WatchKit Tutorial: Communicate from Parent App Back to Watch via Reply

Note: this will be a continuation from my last tutorial on WatchKit – Send Data to your Parent iOS App. If you haven’t done it already, I suggest you start there first.

WatchKit Updates

It’s been about a month since my last WatchKit tutorial and we’ve had 2 new beta releases for iOS 8.2, including a few changes for WatchKit. Here’s the release notes for iOS 8.2 beta 4:

watchkit iOS 8.4 beta 4 release notes

WatchKit still has pretty limited functionality and there haven’t been many new changes – it’ll be interesting to see what kinds of apps people create once it’s live. Looking back on the Apple keynote where they announced the Apple Watch and showed all the cool tricks like being able to send your heartbeat to someone else, I feel a bit cheated as a third-party developer. So far, we don’t have access to any of these interesting features. It’s like when a movie trailer shows all the cool parts of the movie, and the movie itself is pretty lame.

Reply Back to Watch app

Counter Reply

Now, to get down to business – I’ve added on to my original tutorial that allowed you to send data to your parent iOS app from the watch. We’ll be exploring replying back to the Watch app from the parent iOS app. This is useful for cases where you may want to update the UI of your Watch app or execute some code after your iOS app receives data from the Watch. In this case, we’ll be showing the user a “Saved” notification indicating that their save to the iOS app was successful.

You can see the finished project for this tutorial here on Github.

Modify Watch Storyboard

To get started, bring up the finished counter project from the last tutorial. If you haven’t gone through it yet, you can grab it here from Github.

Let’s start with the Watch interface first. We’ll be changing our original interface:

WatchKit App Storyboard File

To this:

Screen Shot 2015-01-14 at 2.29.31 PM First drag in another label to go underneath the counter value and change the color of the text to differentiate it from the counter. I set the text to “Saved N,” but it doesn’t really matter what you put here since you’ll be modifying this value later. This “Saved N” label is going to be our notification to the user, so it should be hidden until a user saves a count value. You can hide this by going to the Attributes inspector in the sidebar (it looks like a bookmark ribbon) and changing the Alpha value to 0.

Attributes inspector

Note that I’ve left the Hidden box unchecked. You could technically use the Hidden option also to hide the label, but there’s a subtle difference between changing the Alpha and setting the Hidden option.

Hidden – if turned on, hides the element and does NOT preserve element spacing
Alpha – if set to 0, hides the element and preserves element spacing

That is, the extra space taken up by the label would disappear if we turned the Hidden attribute on, but stays the same if you just turn the alpha to 0. It’s up to you which one you prefer, but the buttons will keep moving when the Saved notification label toggles visibility so I preferred just preserving the space so the buttons would stay in the same spot.

Next, to get the buttons to appear side-by-side, you’ll need to drag in a Group from the sidebar to your watch interface. Drag it in above the Hit button and you should see a gray dashed outline with the word Group in it. Resize the Hit and Clear buttons so that they’re about half the horizontal size of the screen and drag them into the group. The dashed line should disappear once you drag the first button in.

Screen Shot 2015-01-14 at 3.41.32 PM

That’s it for the watch interface! One last thing you should do is drag an IBOutlet from the (now hidden) Saved notification label into the InterfaceController.h file. I named it this:

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

AppDelegate.m – Change Reply Dictionary

There aren’t many code changes you need to make from the last tutorial to this one – in fact, we already had the reply working in the previous project, but it wasn’t very useful aside from being a good sanity check that the flow was correct.

See this line in the handleWatchKitExtensionRequest method? You can find this in the AppDelegate.m file in the WatchKitCounterDemo folder.

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

In the original tutorial, we logged the key and value out of the dictionary that was sent through this reply. You can see this in the saveCounter method in the InterfaceController.m file:

NSLog(@"%@ %@",replyInfo, error);

We’re going to change this reply to include something that’s a bit more useful. Replace your current handleWatchKitExtentionRequest method with this one:

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply {
    
    NSString *counterValue = [userInfo objectForKey:@"counterValue"];
    NSDictionary *replyDict = @{@"response": counterValue};
    
    reply(replyDict);

    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];

}

Now we’re sending a dictionary with “response” as the key and the counter value as the value. It’s a little redundant, I admit, since the watch extension already has this value, but the point is to show how we can send useful information back to the extension.

Great, that’s done. Now let’s move onto your InterfaceController.m file.

Accept the response – InterfaceController.m

Replace your InterfaceController.m code with this:

//
//  InterfaceController.m
//  WatchKitCounterDemo WatchKit Extension
//
//  Created by Thai, Kristina on 12/10/14.
//  Copyright (c) 2014 Kristina Thai. All rights reserved.
//

#import "InterfaceController.h"


@interface InterfaceController()

@property (nonatomic, assign) int counter;
@end

@implementation InterfaceController

- (void)awakeWithContext:(id)context {
    [super awakeWithContext:context];

    // Configure interface objects here.
    NSLog(@"%@ awakeWithContext", self);
    self.counter = 0;
}

- (void)willActivate {
    // This method is called when watch view controller is about to be visible to user
    NSLog(@"%@ will activate", self);
}

- (void)didDeactivate {
    // This method is called when watch view controller is no longer visible
    NSLog(@"%@ did deactivate", self);
}

#pragma mark - Button actions

- (IBAction)incrementCounter {
    [self hideSaveNotificationLabel];
    
    self.counter++;
    [self setCounterLabelText];
}
- (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) {
        
        int reply = [[replyInfo objectForKey:@"response"] intValue];
        
        //Show and change text for hidden save notification label
        [self.savedNotificationLabel setText:[NSString stringWithFormat:@"Saved %d", reply]];
        [self showSaveNotificationLabel];
    }];

}
- (IBAction)clearCounter {
    [self hideSaveNotificationLabel];
    
    self.counter = 0;
    [self setCounterLabelText];
}

#pragma mark - Helper methods

- (void)setCounterLabelText {
    [self.counterLabel setText:[NSString stringWithFormat:@"%d", self.counter]];
}

- (void)hideSaveNotificationLabel {
    [self.savedNotificationLabel setAlpha:0];
}

-(void)showSaveNotificationLabel {
    [self.savedNotificationLabel setAlpha:1];
}

@end

I’ve added in some helper methods to show and hide the SavedNotificationLabel. The main piece you should pay attention to is in the saveCounter method.

- (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) {
        
        int reply = [[replyInfo objectForKey:@"response"] intValue];
        
        //Show and change text for hidden save notification label
        [self.savedNotificationLabel setText:[NSString stringWithFormat:@"Saved %d", reply]];
        [self showSaveNotificationLabel];
    }];

}

In this case, I know that my response will contain my counter value (normally, this is not the safest thing to do – if there’s not a “response” key in your dictionary, this will crash your app).  I grab this value out of the reply dictionary and I set the text for my Saved label to “Saved” + the counter value. This will un-hide the savedNotificationLabel and notify the user that their save went through successfully, along with the value that they’ve just saved.

That’s it! Run the app and you should see the notification come up every time you hit the Save button on the watch. The notification will hide when the user increments the counter or if they clear the counter.

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.

Expanding this to more variable situations

Now this tutorial was fairly simple in that it showed a reply with only one key/value pair in the dictionary from the iOS AppDelegate and only one path in the reply block back in the watch extension. For more variable purposes, you could imagine replying from handleWatchKitExtentionRequest with a large dictionary filled with many values. You could also use a switch/if & else statement in the reply block in openParentApplication that depends on the type of response you get back. The possibilities here are endless.

As always, feel free to ask any questions below. The inspiration for this post came from a comment from my last tutorial, so don’t be shy.

14 thoughts on “WatchKit Tutorial: Communicate from Parent App Back to Watch via Reply”

  1. I come from China,only can read written document for my poor hearing of English.Your tutorial give me a good start about watchKit.Thank you.

  2. Hi Kristina,

    I following your tutorial regarding how to pass data between the watch and the app 2 way.

    What I don’t understand is that I can share a dictionary with array of integers or strings, but how do I pass an object that is declared in a common object class (assuming both targets (extension and app) and doing the import correctly.

    Could you update your code to show how to pass a more complex object (let’s say with a text, int and date) ?

    Thanks a lot, looking forward to understand if it’s doable, beside passing json string between them of course

  3. Thanks so much for your tutorial… Really helpful. Two questions, if you’ll permit me:
    1) regarding Ilan’s question, I don’t understand the problem… Can you just pass the ints or strings as an entry in the dict? Aren’t arrays of ints or strings NSDict compliant?
    2) Why do you declare the dicts “inline,” in the method calls (on both the WK extension and parent app sides), rather than independently before them?
    Thanks again for your help!

    1. Thanks for your comment! Glad it was helpful =)

      1. Yes, your assumption is correct. You can pass ints/strings and arrays of ints and strings as NSDictionary values. What Ilan was referring to are common object classes, or custom object classes, that that app uses (and that he would like to use in the extension too). There currently isn’t a way to pass a custom object via the reply dictionary (it gives a failing error message that the reply never went through on the extension side). Say Ilan created a custom Person class that stores the name, age and height of a person. He couldn’t pass that Person object in the dictionary, but what he can do is pass the individual attributes (name string, age int and height int) in a dictionary of dictionaries. Does that make sense?

      2. You can declare dicts however you would like (and in a real app, you’ll probably have these dictionaries defined much earlier depending on your needs). This was mainly for clarity’s sake to make the tutorial easy to follow.

      1. Totally makes sense. Sorry if the questions were naive… only been at iOS a few months (old C programmer, trying to reaffirm the adage that old dogs can learn new tricks!). Thanks again for your guidance!

      2. No sooner did I digest your response concerning custom object classes than I had cause to deal with the problem myself! Thought I would share a solution I found with any others following this thread.

        My problem actually arose when trying to pass an attribute (UIColor), which was not allowed (guess it does not adhere to the right standards for copying or some other requirement of NSDictionary?). I found the following link useful, which suggests archiving the data on the iPhone side and then unarchiving on the watch side:

        http://stackoverflow.com/posts/29102932/revisions

        I’m now using this successful to pass all my custom class information. Note, however, that the custom class has to implement the archiving and unarchiving methods.

Leave a Reply

Your email address will not be published. Required fields are marked *