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.
(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
Cannot Get Rid of Optional() String
Images for Retina Screen (@2X)
Alamofire:How to Handle Errors Globally
How to Change Font of Uibutton with Swift
Pod Install Displaying Error in Cocoapods Version 1.0.0.Beta.1
Find Where Object Is Retained with Arc
Xcode 8 Beta - Convert to Current Swift Syntax Failed: Could Not Find Test Host
How to Add Headerview in Uicollectionview Like Uitableview's Tableheaderview
Creating a 3X3 Grid with Auto Layout Constraints
A Launch Storyboard or Xib Must Be Provided Unless the App Requires Full Screen
Status Bar Showing Black Text, Only on iPhone 6 iOS 8 Simulator
Custom Font in iOS Not Working
Xcode: "Scene Is Unreachable Due to Lack of Entry Points" But Can't Find It
Any Way to Install App to iPhone 4 with Xcode 8 Beta
Using Custom Fonts with Xcode 6/iOS 8 Interface Builder Launch Screen
Navigationview and Navigationlink on Button Click in Swiftui