Notify Watchkit App of an Update Without the Watch App Requesting It

Notify WatchKit app of an update without the watch app requesting it

Background

First off all let's sum up what we know.
We have

  • app that runs on iPhone (I will refer to it as iPhone app)
  • app that runs on Watch...specifically
    • UI that runs on Watch
    • code that runs on iPhone as Extension.

First and last lines are most important to us. Yes, Extension is shipped to AppStore with your iPhone app, however this two things can run separately in iOS operating system. Hence, Extension and iPhone app are two different processes - two different programs that runs in OS.

Because of that fact, we can't use [NSNotificationCenter defaultCenter] because when you try to NSLog() defaultCenter on iPhone and defaultCenter in Extension they will have different memory address.

Darwin to the rescue!

As you might imagine, this kind of problem is not new to developers, it's proper term is Interprocess Communication. So in OS X and iOS there is...Darwin Notification mechanism. And the easiest way to use it is to implement few methods from CFNotificationCenter class.

Example

When using CFNotificationCenter you will see it looks very similar to NSNotificationCenter. My guess is NSNotif.. was built around CFNotif.. but I did't confirmed that hypothesis. Now, to the point.

So lets assume you want to send notification from iPhone to Watch back and forth. First thing we should do is to register to notifications.

- (void)registerToNotification
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceivedNSNotification) name:@"com.example.MyAwesomeApp" object:nil];

CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), didReceivedDarwinNotification, CFSTR("NOTIFICATION_TO_WATCH"), NULL, CFNotificationSuspensionBehaviorDrop);
}

You probably wondering why I added observer for NSNotificationCenter? In order to accomplish our task we need to create some loop, you will see it in a moment.

As for second method.

CFNotificationCenterGetDarwinNotifyCenter() - get Darwin Notify Centre

(__bridge const void *)(self) - notification observer

didReceivedDarwinNotification - callBack method, fired when object receives notification.
Basically it's the same as @selector in NSNotification

CFSTR("NOTIFICATION_TO_WATCH") - name of the notification, same story in NSNotification, but here we need CFSTR method to convert string into CFStringRef

And finally last two parameters object and suspensionBehaviour - both ignored when we are using DarwinNotifyCenter.

Cool, so we registered as an observer. So lets implement our callback methods (there are two of them, one for CFNotificationCenter, and one for NSNotificationCenter).

void didReceivedDarwinNotification()
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"com.example.MyAwesomeApp" object:nil];
}

Now, as you see, this method doesn't starts with - (void)Name.... Why? Because it's C method. Do you see why we need NSNotificationCenter here? From C method we don't have access to self. One option is to declare static pointer to yourself, like this: static id staticSelf assign it staticSelf = self and then use it from didReceivedDarwinNotification: ((YourClass*)staticSelf)->_yourProperty but I think NSNotificationCenter is better approach.

So then in selector that responds to your NSNotification:

- (void)didReceivedNSNotification
{
// you can do what you want, Obj-C method
}

When we are, finally, registered as observer we can send something from iPhone app.

For this we need only one line of code.

CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("NOTIFICATION_TO_WATCH"), (__bridge const void *)(self), nil, TRUE);

which can be in your ViewController, or Model.

Again, we want to get CFNotificationCenterGetDarwinNotifyCenter(), then we specify name for notification, object that is posting notification, dictionary object (ignored when using DarwinNotifyCenter and last parameters is answer to question: deliver immediately?

In similar fashion you can send notification from Watch to iPhone. From obvious reason I suggest using different notification name, like CFSTR("NOTIFICATION_TO_IPHONE") to avoid situation where, for example, iPhone sends notification to Watch and to itself.

To sum up

MMWormhole is perfectly fine and well written class, even with tests that covers most, if not all, code. It's easy to use, just remember to setup your AppGroups before.
However, if you don't want to import third-party code to your project or you don't want to use it for some other reason, you can use implementation provided in this answer.
Especially if you don't want to/need to exchange data between iPhone and Watch.

There is also second good project LLBSDMessaging. It's based on Berkeley sockets. More sophisticated and based on more low-level code. Here is link to lengthy but well written blog post, you will find link to Github there. http://ddeville.me/2015/02/interprocess-communication-on-ios-with-berkeley-sockets/.

Hope this help.

Watch-kit : adding Notifications Scene to existing watchkit app ios

No you do not have to create another target to handle notifications. All you need to do is to add the Notification Interface Controller object to your Interface.stroyboard file plus a .apns file you need to create which would have been created by Xcode if you have selected the option during project creation.

Add a Notification Interface Controller object from object library and select the option Has Dynamic Interfaces if you are implementing a dynamic notification scene. This will add another scene automatically to your stroyboard.

Check the Apple Documentation for details and Tutorial to set up remote notification in Watch App. Though the tutorial doesn't add the notification scene separately but you will get the idea https://www.natashatherobot.com/watchkit-actionable-notifications

watch kit showing the updated data from the server

I had the same scenario to perform. So what i did in my App is:

In your didFinishLaunchingWithOptions method in AppDelegate.m

timer =  [NSTimer scheduledTimerWithTimeInterval:120 target:self selector:@selector(refreshData) userInfo:nil repeats:YES];

refreshData method looks like

-(void)refreshData
{
// Update Database Calls
// below line Save new time when update request goes to server every time.
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:@"databaseUpdateTimestamp"]; //
}

Now Add a timer in your willActivate method in watchKit

 timer = [NSTimer scheduledTimerWithTimeInterval:120 target:self selector:@selector(refreshData) userInfo:nil repeats:YES]; 

the refreshData method will call request to parent App every 2 min.

- (void) refreshData
{
NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:@"database",@"update", nil];
[WKInterfaceController openParentApplication:dic reply:^(NSDictionary *replyInfo, NSError *error)
{
NSLog(@"%@ %@",replyInfo, error);
}];
}

Now in your App Delegate in Parent App

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply
{
if([userInfo objectForKey:@"update"])
{
NSString *strChceckDB = [userInfo objectForKey:@"update"];
if ([strChceckDB isEqualToString:@"database"])
{
NSDate *dateNow = [NSDate date];
NSDate *previousUpdatedTime = (NSDate*)[[NSUserDefaults standardUserDefaults] objectForKey:@"databaseUpdateTimestamp"];
NSTimeInterval distanceBetweenDates = [dateNow timeIntervalSinceDate:previousUpdatedTime];
if (distanceBetweenDates>120) // this is to check that no data request is in progress
{
[self.timer invalidate]; // your orignal timer in iPhone App
self.timer = nil;
[self refreshData]; //Method to Get New Records from Server
[self addTimer]; // Add it again for normal update calls
}
}
}
else
{
//do something else
}
}

Use This Updated Data to Populate your apps dashboard in WatchKit App.

Some Useful Links That will help you to complete this:

You can create an embedded framework to share code between your app extension and its containing app.

Adding an App to an App Group

Hope this help you ....!!!

Easy way to update app content via apple watch

You weren't lucky because when you wrote your question there wasn't API for this in iOS SDK. Three days ago, 10th December 2014 Apple released iOS 8.2 beta 2 SDK with two, important for this task, methods.

In WatchKit Framework, WKInterfaceController class

// Obj-C
+ (BOOL)openParentApplication:(NSDictionary *)userInfo
reply:(void (^)(NSDictionary *replyInfo,
NSError *error))reply

By calling this method iOS will run your app in the background and AppDelegate of app will receive this message

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply. 

This is second method added in iOS SDK Beta 2 (in terms of this question) in UIKit Framework, UIApplicationDelegate class.

You can use NSDictionary and reply block to communicate Watch app and iOS app.

Example

In your WKInterfaceController subclass

- (IBAction)callPhoneAppButtonTapped
{
NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:@"text to display", @"key", nil];

[InterfaceController openParentApplication:dictionary reply:^(NSDictionary *replyInfo, NSError *error) {
NSLog(@"Reply received by Watch app: %@", replyInfo);
}];
}

and then in your iOS AppDelegate class

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
NSLog(@"Request received by iOS app");
NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:@"your value to return to Apple Watch", @"key", nil];

reply(dictionary);
}

When you tap button on Apple Watch simulator your iOS app in iOS Simulator will be launched, and you should be able to see NSLog's in proper places.

Note

This solution works for transporting objects between Watch and iOS apps.
But if you plan to transport more data, access images, file etc, you should use Shared app group. You set shared app group in Capabilities in your Xcode Project file.
Use containerURLForSecurityApplicationGroupIdentifier (NSFileManager class) to get URLs to files in shared group.

If you want to share Preferences initWithSuiteName from NSUserDefaults is what you are looking for.

Payload for the Watch App Notification

In the Local and Remote Notification Programming Guide(Table 3-1), the value type of alert key can be string or dictionary, as Dhawal said, both formats are correct.

If alert is dictionary, it can contain title, body, title-loc-key etc(Table 3-2). What's the purpose of title key? This key was added in iOS 8.2 which contain WatchKit, and WatchKit has a Short-Look Notification interface, there is no enough space for full notification, so Apple Watch use title to describe the purpose of the notification and display in Short-Look Notification.

Sample Image

(source: edgekey.net)

In this picture, "Gray's Birthday" is the title in alert. Because you can't see Short-Look Notification in simulator, you should test result of title key in REAL Apple Watch.

Apple Watch - how do I schedule a complication update to be in synch with Push notification?

You can implement Background Fetch and force the complication update, this way you will have next schemes:

iOS:
Implement application:didReceiveRemoteNotification:fetchCompletionHandler: on the UIApplicationDelegate and fetch there a push notification. After that transfer fetched data using WatchConnectivity to Watch and force the complication update.

watchOS:
Implement didReceiveRemoteNotification: on the WKExtensionDelegate and just force the complication update after the successful fetching.



Related Topics



Leave a reply



Submit