Share data between iOS 9 and WatchOS 2 in real time



  • Stacks Image 2752
    Image slide caption
  • Stacks Image 2753
    Image slide caption
In Watch OS1 we used “App Groups” to sync data. With the appearance with WatchOS2, that became deprecated.

With WatchOS 2 WCSession made its appearance and that is the new mantra for communicating with Apple Watch.

All you need is at least Xcode 7. The completed project can be downloaded at GitHub here

Start

We are building an iPhone App with a Watch Extension, both will have switch and slider item that we will sync. If move the slider on one device, tap the sync button, the values should sync on both devices. We be using the NSUserDefaults class and WCSession to accomplish this.

Rather then doing this as a step by step tutorial, download the sample project can be found on GitHub at
https://github.com/theogscott/SyncUserDefaults, and I talk you through the crucial things. I am going to assume you are already familiar with Xcode how to create a Watch extension project, how to create an IBOutlet & IBAction.

You will need at least Xcode 7 with the iOS 9 SDK and Watch OS2 SDK.


The SyncUserDefaults Class


@interface SyncUserDefaults : NSUserDefaults

+ (SyncUserDefaults *)standardUserDefaults;
- (BOOL)synchronize;
- (NSString *) syncUserDefaultsKeyName;
- (void)importValuesFromCompanionApp: (NSDictionary *) syncedUserDefaults;

@end
We will be creating a new class called SyncUserDefaults derived from NSUserDefaults. We use the same class in both iOS and Watch OS implementations, so no code duplication.

We will be overriding two methods:
  • standardUserDefaults to keep consistency with NSUserDefaults
  • synchronize that will call synchronize in NSUserDefaults plus initiate any data transfers to the companion app.

Create 2 new public methods
  • syncUserDefaultsKeyName that the key of the single entry in dictionary for another dictionary that contain the items we want to sync. We will name it SyncUserDefaultsKey. Yes we be sending a dictionary of items to the Apple Watch and vice versa (within another dictionary).
  • importValuesFromCompanionApp will take a dictionary as a parameter and copy the diction given by the argument/parameter to the NSUserDefaults.

create the following private methods
  • getKeysToSyncronizeWithCompanion that is a set of keys we to sync.
  • exportedDefaultsForCompanion creates the dictionary of items to be pushed to the companion App.
  • exportedDefaultsForCompanion is the opposite of importValuesFromCompanionApp: it will export the local dictionary, defined by SyncUserDefaultsKey, and make dictionary copy, to be sent to the companion App.
  • pushValues will transfer the dictionary created by exportedDefaultsForCompanion. to the company App.


Creating a Watch Kit Communication Session

In both the iOS and Watch OS part you have to create and activity a sessions, usually starting with an IF statement to determined if WCSession is Supported
In your iOS ViewController.m add the
< WCSessionDelegate > to ViewController by changing and adding such that it becomes


#import 

@interface ViewController ()  

@end
and add the IF statement to viewDidLoad :

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    if ([WCSession isSupported])
    {
        WCSession *session = [WCSession defaultSession];
        session.delegate = self;
        [session activateSession];
    }
    [self readDefaults];
}
and in the WatchOS InterfaceController.m add < WCSessionDelegate > to InterfaceController by changing and adding such that it becomes

#import < WatchConnectivity/WatchConnectivity.h >

@interface InterfaceController() < WCSessionDelegate >
{
}

@end

and add the IF statement to awakeWithContext :

- (void)awakeWithContext:(id)context {
    [super awakeWithContext:context];
    
    if ([WCSession isSupported])
    {
        WCSession *session = [WCSession defaultSession];
        session.delegate = self;
        [session activateSession];
    }
    // Configure interface objects here.
    [self readDefaults];
    
}

The readDefaults is a method to read values from the “shared dictionary” and populate, in this examples some GUI objects. We will get to it later on.


Receive and handling data from the companion app

In both InterfaceController.m and ViewController.m add the following to handle WCSessionDelegate
The code below is extracted from InterfaceController.m, so you might want to replace NSLog line to NSLog(@"iPhone received values") ; for ViewController.m code.

- (void)session:(WCSession *)session didReceiveApplicationContext: (NSDictionary *)applicationContext;
{
    SyncUserDefaults *UserDefaults=[SyncUserDefaults standardUserDefaults];
    NSLog(@"Watch received values");
    NSDictionary *settings = [applicationContext objectForKey:
                              [UserDefaults syncUserDefaultsKeyName]];
    if (settings != nil)
    {
        // Import the settings
        dispatch_async(dispatch_get_main_queue(), ^{
            [UserDefaults  importValuesFromCompanionApp:settings];
            [self readDefaults];
        });
    }
}


readDefaults

The readDefaults method you see in the above code is simply to read the values from SyncUserDefaults (stored in a NSUserDefaults subclassed object) and display them on the screen. I call it when the is loaded and when I receive new data from the companion App. Below is the code for InterfaceController.m:



- (void) readDefaults
{
    SyncUserDefaults *UserDefaults=[SyncUserDefaults standardUserDefaults];

    [self.value1 setOn:[UserDefaults boolForKey:@"Value1"]];
    [self.value2 setText:[UserDefaults objectForKey:@"Value2"]];
    [self.value3 setValue:[UserDefaults floatForKey:@"Value3"]];
}


Synchronization

There are two ways to synchronize UserDefaults:
  • Synchronize the data with a event. In my case this when the user tapped the “sync” button
  • Another way to Synchronize as soon as the value has changed. This might require more elegant code in order to minimise unnecessary data transfers.


Demo

To see how the code works, see the video recording on YouTube here
You can also find the complete code and project on GitHub
here


Wrapping up

I hope the above might have helped you.There are a few things you might want to consider when you use the same methodology as above are:
  1. You can optimise the code by tracking which variables has changed, or check if NSUserDefaults has dome mechanisms you might find useful.
  2. You might want to synchronise each time a variable changed it value, but watch out if you going to do on something like a slider; you don’t want sync on every click but rather be sure the user has completed the update.
  3. And remember in real life the watch and iPhone combinations happen via bluetooth and interference might slot down that you otherwise won’t detect in the simulators.