watchOS

watchOS 2 Tutorial: Using application context to transfer data (Watch Connectivity #2)

Here’s part 2 of my Watch Connectivity tutorial series. We’ll be looking at using the application context option to transfer data between the Apple Watch and your iPhone. For anyone that needs a refresher on the different options to transfer data with Watch Connectivity, you can refer to my earlier post.

A note before we begin – I’d like to thank those of you that have written to me about how much you appreciate these tutorials in both Swift and Objective-C. I’m glad to know that it’s helping a lot of you learn Swift for the very first time (me included), along with Apple Watch development. If there’s anything else you’d like to see, don’t hesitate to tweet me or send me an email 😀

Application Context

In brief, application context is best used for transferring data that always needs to be updated to the latest information. This is important because, for all of the data that you’re transferring over, only the latest copy of that data will actually appear at your final destination. This is useful for things like profile data updates that are always overwritten every time.

We’ll be looking at a simple app that shows the last emoji that a user has selected from their watch. See the final projects on GitHub in both Swift and Objective-C.

emoji

As always, let’s start by creating our interface first.

Interface.storyboard

Model your watch interface after mine. Drag out 3 groups and 6 buttons to create this interface. Feel free to choose any emojis you’d like. For your reference, I chose cat, dog, panda, rabbit, cow and hamster.

To get the buttons to always be half the width of the interface, make sure to set the width to “relative to container” at .5 or 50% of the width of the container. You can do this in the sidebar when a button is selected.

Screen Shot 2015-08-09 at 6.40.32 PM

Interfacecontroller.m/interfacecontroller.swift

Next, open up the split view editor in Xcode. We need to create IBActions for each one of the emoji buttons. Connect each button to its appropriate function:

//Swift
@IBAction func catPressed() {
    sendEmoji("cat")
}
@IBAction func dogPressed() {
    sendEmoji("dog")
}
@IBAction func pandaPressed() {
    sendEmoji("panda")
}
@IBAction func bunnyPressed() {
    sendEmoji("bunny")
}
@IBAction func cowPressed() {
    sendEmoji("cow")
}
@IBAction func hamsterPressed() {
    sendEmoji("hamster")
}
//Objective-C
- (IBAction)catPressed {
    [self sendEmoji:@"cat"];
}
- (IBAction)dogPressed {
    [self sendEmoji:@"dog"];
}
- (IBAction)pandaPressed {
    [self sendEmoji:@"panda"];
}
- (IBAction)bunnyPressed {
    [self sendEmoji:@"bunny"];
}
- (IBAction)cowPressed {
    [self sendEmoji:@"cow"];
}
- (IBAction)hamsterPressed {
    [self sendEmoji:@"hamster"];
}

You can close the split view now and just focus on InterfaceController.m/InterfaceController.swift. We haven’t created the sendEmoji function yet, but don’t worry, we’ll get to it.

As with all watch connectivity data transfer options, we need to create a WCSession in order to transfer information between the watch and phone. We’ll create an init function for this. First, import Watch Connectivity and add WCSessionDelegate to the interface header like so:

//Swift
import WatchConnectivity
class InterfaceController: WKInterfaceController, WCSessionDelegate {
//Objective-C
#import <WatchConnectivity/WatchConnectivity.h>
@interface InterfaceController() 

Add an init function with the following:

//Swift

var session : WCSession!

override init() {
        super.init()
        if (WCSession.isSupported()) {
            session = WCSession.defaultSession()
            session.delegate = self
            session.activateSession()
        }
}
//Objective-C
-(instancetype)init {
    self = [super init];
    
    if (self) {
        if ([WCSession isSupported]) {
            self.session = [WCSession defaultSession];
            self.session.delegate = self;
            [self.session activateSession];
        }
    }
    return self;
}

Now, we’re going to get to the part where we start the emoji data transfer. Add the sendEmoji after all of your IBAction functions.

//Swift
func sendEmoji(emoji: String) {
    let applicationDict = ["emoji":emoji]
    do {
        try session.updateApplicationContext(applicationDict)
    } catch {
        print("error")
    }
}
//Objective-C
-(void)sendEmoji:(NSString *)emoji {
    NSDictionary *applicationDict = @{@"emoji":emoji};
    [self.session updateApplicationContext:applicationDict error:nil];
}

Here, we’re creating a dictionary with the emoji that has been pressed and passing it to updateApplicationContext:error. This will send our dictionary over to the phone.

Main.storyboard

Next, we’ll create some basic UI to display the text for the last emoji we pressed on the phone.

 

Drag a UILabel in from the sidebar and center it in the middle of screen. You can do this by checking the center horizontally and vertically in container alignment constraints. It doesn’t really matter what the text here is like since we’ll be replacing it programmatically.

ViewController.m/ViewController.swift

Open up the split view again with Main.storyboard and ViewController.m/.swift.  Replace your entire ViewController.m/.swift file with the following code:

//Swift
import UIKit
import WatchConnectivity

class ViewController: UIViewController, WCSessionDelegate {

    @IBOutlet weak var emojiLabel: UILabel!
    var session : WCSession!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        if (WCSession.isSupported()) {
            session = WCSession.defaultSession()
            session.delegate = self;
            session.activateSession()
        }
    }

    func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
        let emoji = applicationContext["emoji"] as? String
        
        //Use this to update the UI instantaneously (otherwise, takes a little while)
        dispatch_async(dispatch_get_main_queue()) {
            self.emojiLabel.text = "Last emoji: " + emoji!
        }
    }
}

//Objective-C
#import "ViewController.h"
#import <WatchConnectivity/WatchConnectivity.h>

@interface ViewController () 
@property (weak, nonatomic) IBOutlet UILabel *emojiLabel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    if ([WCSession isSupported]) {
        WCSession *session = [WCSession defaultSession];
        session.delegate = self;
        [session activateSession];
    }
}

- (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary *)applicationContext {
    
    NSString *emoji = [applicationContext objectForKey:@"emoji"];
    
    //Use this to update the UI instantaneously (otherwise, takes a little while)
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.emojiLabel setText:[NSString stringWithFormat:@"Last emoji: %@", emoji]];
    });
}

Connect the UILabel from your storyboard to the emojiLabel outlet. Go ahead and close the split view to focus solely on the view controller. Note that we’re setting up our session in this file as we did in the watch extension.

The main function you’ll want to take note of is the didReceiveApplicationContext:applicationContext one. Here, we’re receiving the emoji dictionary that we sent over from the watch. It’s pretty simple from here – we just pull the emoji string out of the dictionary and use it to update the text on our phone app UI to display the last emoji name.

That’s it! Build and run the app. Click on a few of the watch emoji buttons and keep track of the last one you pressed. That should be the name of the one that shows up in your phone app.

🐱


Let me know if there’s any questions/comments and I’ll follow up as soon as possible.

 

22 thoughts on “watchOS 2 Tutorial: Using application context to transfer data (Watch Connectivity #2)”

  1. Correct me if I am wrong but I don’t think you should initialize WCSession in willActivate. wiiActivate will get called every time when you raise your wrist. If you look at the Potloc example from Apple they called it in init of the class.

  2. Hi Kristina, I am very interested in anything to do with iOS especially Apple Watch as I am both learning Swift and Objective C. I have completed your project WatchConnectivity but have a few problems which I am struggling to deal with. I have check all my code which is correct, i have then downloaded your code from Github but still having the same error message. Basically when I add the line import WatchConnectivity, in the ViewController.swift file, I received a error message saying “cannot import module being compiled” This then has a impact with my session. I am using Version 7.0 beta 5 (7A176x). When I command click on the import WatchConnectivity, a box comes up with a large question mark and no information. It code seems fine on the InterfaceController.swift file. Any suggestions?

  3. This is a very helpful tutorial; thank you, Kristina! Is there any chance of seeing a tutorial that demonstrates sending more than just one string of data (I.E. the tapped emoji)? What if you download some JSON data via a Weather API; how would that get passed to the watch? Thanks!

    1. Alan, it is easy to map JSON data to an NSDictionary, and send it over to the watch via the WatchConnectivity framework. You can use the NSJSONSerializer class to do that. Since the data is coming from a JSON structure, the dictionary will be populated with the object types that can be sent over with WC.

  4. Thanks for this tutorial it’s great and thanks for writing in Objective C too 🙂
    just one question is that work this method in background and foreground ?
    because i will open the 2 device simulator for testing and i need work in background too.

    thanks ahead for your great tutorial you are the best Kristina Thai

    Best regards
    Milad

  5. Hi , and Excuse Kristina Thai

    one Question for Background system work

    when i switch this code (down) ViewdidLoad -> To App delegate.M

    in app Delegate

    – (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {

    if ([WCSession isSupported]) {
    self.session = [WCSession defaultSession];
    self.session.delegate = self;
    [self.session activateSession];
    }

    }

    after this function does not call in ViewController

    what can i do for call session in App delegate.M with your good example

    – (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary *)applicationContext {
    ////
    }

    1. Excuse your codes its work great in background and foreground i need little wait for receiving result in Watch 🙂 ViewdidLoad it’s good place for ( WCSession ) 🙂

      Best regards
      Milad

  6. This is awesome. Quick question, I’m getting an error on the line `try session? …’ in `sendEmoji` saying “Use of unresolved identifier session”, any ideas what I messed up there? Thanks!

  7. Your posts are super simple and direct to the point. I wish I had found your blogs 6 months back. You have examples for pretty much all the stuff I had to break my head for.

    Great work! Keep writing.

  8. Hi Christina, I happily had my phone talking to my watch under OS1. Converted to OS2 and I am stuck. I receive the NSDictionary from the Phone, all OK. I set a label.text on the Watch to an incoming parameter. All OK except the UI does not refresh and so the data is not displayed. I saw you were pushing to the main thread, which I have tried, but with no impact. Have you experienced this ?

    -(void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary *)applicationContext
    {
    dispatch_async(dispatch_get_main_queue(), ^{
    [self.lblTitleBorH setText:@”test”];
    });
    }

Leave a Reply

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