Search code examples
ioswatchkitwatchos-2

Using WCSession with more than one ViewController


I found many questions and many answers but no final example for the request:

Can anyone give a final example in Objective C what is best practice to use WCSession with an IOS app and a Watch app (WatchOS2) with more than one ViewController.

What I noticed so far are the following facts:

1.) Activate the WCSession in the parent (IOS) app at the AppDelegate:

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //Any other code you might have

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

2.) On the WatchOS2 side use <WCSessionDelegate>. But the rest is totally unclear for me! Some answers are talking from specifying keys in the passing Dictionary like:

[session updateApplicationContext:@{@"viewController1": @"item1"} error:&error];
[session updateApplicationContext:@{@"viewController2": @"item2"} error:&error];

Others are talking about retrieving the default session

WCSession* session = [WCSession defaultSession];
[session updateApplicationContext:applicationDict error:nil];

Others are talking about different queues? "It is the client's responsibility to dispatch to another queue if necessary. Dispatch back to the main."

I am totally confused. So please give an example how to use WCSession with an IOS app and a WatchOS2 App with more than one ViewController.

I need it for the following case (simplified): In my parent app I am measuring heart rate, workout time and calories. At the Watch app 1. ViewController I will show the heart rate and the workout time at the 2. ViewController I will show the heart rate, too and the calories burned.


Solution

  • As far as I understand the task you just need synchronisation in a Phone -> Watch direction so in a nutshell a minimum configuration for you:

    Phone:

    I believe the application:didFinishLaunchingWithOptions: handler is the best place for the WCSession initialisation therefore place the following code there:

    if ([WCSession isSupported]) {
        // You even don't need to set a delegate because you don't need to receive messages from Watch.
        // Everything that you need is just activate a session.
        [[WCSession defaultSession] activateSession];
    }
    

    Then somewhere in your code that measures a heart rate for example:

    NSError *updateContextError;
    BOOL isContextUpdated = [[WCSession defaultSession] updateApplicationContext:@{@"heartRate": @"90"} error:&updateContextError]
    
    if (!isContextUpdated) {
        NSLog(@"Update failed with error: %@", updateContextError);
    }
    

    update:

    Watch:

    ExtensionDelegate.h:

    @import WatchConnectivity;
    #import <WatchKit/WatchKit.h>
    
    @interface ExtensionDelegate : NSObject <WKExtensionDelegate, WCSessionDelegate>
    @end
    

    ExtensionDelegate.m:

    #import "ExtensionDelegate.h"
    
    @implementation ExtensionDelegate
    
    - (void)applicationDidFinishLaunching {
        // Session objects are always available on Apple Watch thus there is no use in calling +WCSession.isSupported method.
        [WCSession defaultSession].delegate = self;
        [[WCSession defaultSession] activateSession];
    }
    
    - (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,id> *)applicationContext {
         NSString *heartRate = [applicationContext objectForKey:@"heartRate"];
    
        // Compose a userInfo to pass it using postNotificationName method.
        NSDictionary *userInfo = [NSDictionary dictionaryWithObject:heartRate forKey:@"heartRate"];
    
        // Broadcast data outside.
        [[NSNotificationCenter defaultCenter] postNotificationName: @"heartRateDidUpdate" object:nil userInfo:userInfo];
    }
    
    @end
    

    Somewhere in your Controller, let's name it XYZController1.

    XYZController1:

    #import "XYZController1.h"
    
    @implementation XYZController1
    
    - (void)awakeWithContext:(id)context {
        [super awakeWithContext:context];
    
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleUpdatedHeartRate:) name:@"heartRateDidUpdate" object:nil];
    }
    
    -(void)handleUpdatedHeartRate:(NSNotification *)notification {
            NSDictionary* userInfo = notification.userInfo;
            NSString* heartRate = userInfo[@"heartRate"];
            NSLog (@"Successfully received heartRate notification!");
    }
    
    @end
    

    Code hasn't been tested I just wrote it as is so there can be some typos.

    I think the main idea now is quite clear and a transfer of remaining types of data is not that tough task.

    My current WatchConnectivity architecture much more complicated but nevertheless it is based on this logic.

    If you still have any questions we might move a further discussion to the chat.