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.
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.
Well, this is simplified version of my solution as requested by Greg Robertson. Sorry it's not in Objective-C anymore; I'm just copy-pasting from existing AppStore-approved project to make sure there will be no mistakes.
Essentially, any WatchDataProviderDelegate can hook to data provider class as that provides array holder for delegates (instead of one weak var). Incoming WCSessionData are forwarded to all delegates using the notifyDelegates() method.
// MARK: - Data Provider Class
class WatchDataProvider: WCSessionDelegate {
// This class is singleton
static let sharedInstance = WatchDataProvider()
// Sub-Delegates we'll forward to
var delegates = [AnyObject]()
init() {
if WCSession.isSupported() {
WCSession.defaultSession().delegate = self
WCSession.defaultSession().activateSession()
WatchDataProvider.activated = true;
}
}
// MARK: - WCSessionDelegate
public func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
processIncomingMessage(userInfo)
}
public func session(session: WCSession, didReceiveApplicationContext applicationContext: [String: AnyObject]) {
processIncomingMessage(applicationContext)
}
func processIncomingMessage(dictionary: [String:AnyObject] ) {
// do something with incoming data<
notifyDelegates()
}
// MARK: - QLWatchDataProviderDelegate
public func addDelegate(delegate: AnyObject) {
if !(delegates as NSArray).containsObject(delegate) {
delegates.append(delegate)
}
}
public func removeDelegate(delegate: AnyObject) {
if (delegates as NSArray).containsObject(delegate) {
delegates.removeAtIndex((delegates as NSArray).indexOfObject(delegate))
}
}
func notifyDelegates()
{
for delegate in delegates {
if delegate.respondsToSelector("watchDataDidUpdate") {
let validDelegate = delegate as! WatchDataProviderDelegate
validDelegate.watchDataDidUpdate()
}
}
}
}
// MARK: - Watch Glance (or any view controller) listening for changes
class GlanceController: WKInterfaceController, WatchDataProviderDelegate {
// A var in Swift is strong by default
var dataProvider = WatchDataProvider.sharedInstance()
// Obj-C would be: @property (nonatomic, string) WatchDataProvider *dataProvider
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
dataProvider.addDelegate(self)
}
// WatchDataProviderDelegate
func watchDataDidUpdate() {
dispatch_async(dispatch_get_main_queue(), {
// update UI on main thread
})
}}
}
class AnyOtherClass: UIViewController, WatchDataProviderDelegate {
func viewDidLoad() {
WatchDataProvider.sharedInstance().addDelegate(self)
}
// WatchDataProviderDelegate
func watchDataDidUpdate() {
dispatch_async(dispatch_get_main_queue(), {
// update UI on main thread
})
}}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With