How to open up the contacts of iPhone programmatically in watch extension as we do in iOS using AddressBook.
Thanks in advance
In general to communicate with iPhone from your WatchKit extension you use
+ (BOOL)openParentApplication:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo, NSError *error)) reply; // launches containing iOS application on the phone. userInfo must be non-nil
method of WKInterfaceController class.
So for example, you can attach IBAction from your button in Storyboard to this method
- (IBAction)callPhoneAppButtonTapped
{
NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:#"text to display on iPhone", #"key", nil];
[InterfaceController openParentApplication:dictionary reply:^(NSDictionary *replyInfo, NSError *error) {
NSLog(#"Reply received by Watch app: %#", replyInfo);
}];
}
Note: In order to fetch data from Address Book user needs to grant your app permission. But app will be launched in background and user will be focused on Watch so it will be better to ask for this permission in your iPhone app.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
ABAddressBookRequestAccessWithCompletion(ABAddressBookCreateWithOptions(NULL, nil), ^(bool granted, CFErrorRef error) {
if (!granted){
NSLog(#"Access denied");
return;
}
NSLog(#"Access granted");
});
}
In order to handle message sent by openParentApplication:reply in your AppDelegate implement
- (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];
// Here your app will be launch in background. Fetch AddressBook or other data you need.
// Remember to call reply block in the end.
// Example of saving data to Address Book
NSString *firstName;
NSString *lastName;
firstName = #"Maggie";
lastName = #"Peggie";
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, nil);
ABRecordRef contact = ABPersonCreate();
ABRecordSetValue(contact, kABPersonFirstNameProperty, (__bridge CFStringRef) firstName, nil);
ABRecordSetValue(contact, kABPersonLastNameProperty, (__bridge CFStringRef)lastName, nil);
ABAddressBookAddRecord(addressBookRef, contact, nil);
ABAddressBookSave(addressBookRef, nil);
reply(dictionary);
}
While the current versions of Apple Watch apps cannot themselves execute code, your WatchKit Extension runs on the phone and can access all of the iPhone APIs that a standard iOS application can. As developers, we are much more limited in how we can programmatically change the interface, but not in what is done in terms of accessing services.
Therefore, there is no technical requirement to access Address Book data via your iOS app—you could make these requests for Address Book data directly. If these methods execute rapidly, the choice of whether to do this directly in the Extension or in your iPhone app would come down to decisions about what would minimise code complexity and thus maximise code maintainability. Apple have indicated that latency in communication between the iPhone app and WatchKit Extension can largely be ignored as it will be trivial. (It is latency between the Extension, running on the phone, and the Watch app that we need to be focussed on.)
However, we have also been told that WatchKit Extensions may be immediately terminated when Watch apps are, and we need to be prepared for engagement time measured in seconds, not minutes. WatchKit Extensions are not given the kind of latitude that iPhone apps are to complete things in the background after the user interface has terminated. Therefore, the recommendation is that anything that may be more time consuming or which needs to be completed for data integrity should be run in the iPhone app. lvp's answer gives code that could assist with that.
Your app runs on the phone, so you can fetch the contacts and send it to watch
Related
I have a problem regarding share data from iPhone app to apple watch. I have try below code to share NSMutablearray to apple watch but its not working.
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:arrStartScore];
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.com.test.StartScore"];
[defaults setObject:encodedObject forKey:#"WatchHomeViewTableList"];
[defaults synchronize];
For Retrieve Data to apple watch
NSUserDefaults *myDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.com.test.StartScoreCheck"];
arrStartScore = [myDefaults objectForKey:#"WatchHomeViewTableList"];
NSLog(#"dict....%#",arrStartScore);
To send data from phone to watch, use this code.
WCSession *session = [WCSession defaultSession];
NSError *error;
[session updateApplicationContext:#{#"message": yourArray} error:&error];
To receive data from phone on the watch:
- (void) session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,NSMutableArray *> *)applicationContext {
}
you can access your array in didReceiveApplicationContext using
[applicationContext objectForKey:#"message"];
watchOS3 have separated UserDefaults on watch and phone. AppGroups are can share data among one developer's applications within one device. In order to transfer data between devices, use WatchConnectivity framework.
Documentation
You can share data using WatchKit Connectivity Framework. There are different ways you can use for background or interactive.
Check this video by apple it explains all the different ways to communicate between iPhone and Watch.
Also, you can create a singleton class of watch connectivity and use it in both iOS and WatchOS. Establish the session in both the device and integrate the delegate methods and you are done. You can now send and receive messages to and fro from iOS and WatchOS.
I'm looking to send data to my complication as part of a didReceiveRemoteNotification to update the data displayed but there seems to be little documentation from Apple on how to setup the relationship between this and the complication itself.
When a ComplicationController is created, am I supposed to create a WCSession as well and begin listening for the delegate calls? I'm managed to place it into getPlaceholderTemplateForComplication and this seems to work when the iOS application is running but not when the app has been killed (or no longer running).
I'm curious if anyone has a good guide for getting data to the watch as part of a remote JSON push notification when the iOS app is running or not.
I'd recommend watching the WatchConnectivity session from WWDC as it covers updating complications quite a bit towards the end.
In summary, in the iOS app once you have the contents to send:
NSDictionary *userInfo = // data to send
[[WCSession defaultSession] transferComplicationUserInfo:userInfo];
...
- (void)session:(WCSession * __nonnull)session didFinishUserInfoTransfer:(WCSessionUserInfoTransfer *)userInfoTransfer error:(nullable NSError *)error {
// handle error
NSLog(#"%s %# (%#)", __PRETTY_FUNCTION__, userInfoTransfer, error);
}
and on the watch side:
#property WCSession *session;
...
_session = [WCSession defaultSession];
_session.delegate = self;
[_session activateSession];
...
- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *, id> *)userInfo {
// persist data and trigger reload/extend of complication(s)
}
I have a button in WatchKit that sends a notification to the main iPhone app like this.
-(IBAction) startSound
{
//turn sound on
NSString *requestString = [NSString stringWithFormat:#"startSound"]; // This string is arbitrary, just must match here and at the iPhone side of the implementation.
NSDictionary *applicationData = [[NSDictionary alloc] initWithObjects:#[requestString] forKeys:#[#"startSound"]];
[WKInterfaceController openParentApplication:applicationData reply:^(NSDictionary *replyInfo, NSError *error) {
//NSLog(#"\nReply info: %#\nError: %#",replyInfo, error);
}];
}
In my iPhone app delegate I have added the following code.
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply
{
NSLog(#"handleWatchKitExtensionRequest ...");
NSMutableDictionary *mutDic = [[NSMutableDictionary alloc] init];
//This block just asks the code put after it to be run in background for 10 mins max
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
bgTask = UIBackgroundTaskInvalid;
}];
NSString *request = [userInfo objectForKey:#"startSound"];
if ([request isEqualToString:#"startSound"])
{
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource: #"warning" ofType: #"mp3"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
myAudioPlayer1 = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
myAudioPlayer1.numberOfLoops = -1; //inifinite
[myAudioPlayer1 play];
}
reply(nil); //must reply with something no matter what
//once code is all done and the reply has been sent only then end the bg-handler
[application endBackgroundTask:bgTask];
}
Yet, when my app went for apple review, it got rejected for reasons that my app had to be running in the foreground for the sound feature to work. What did I miss?
10.6 - Apple and our customers place a high value on simple, refined, creative, well thought through interfaces. They take more work but are
worth it. Apple sets a high bar. If your user interface is complex or
less than very good, it may be rejected
10.6 Details
We still found that your Apple Watch app requires the containing app
to be running in the foreground on iPhone in order to play siren
sounds, which provides a poor user experience.
Next Steps
Please see the UIApplicationDelegate Protocol Reference to implement
this method and use it to respond to requests from the Apple Watch
app.
Because this method is likely to be called while your app is in the
background, call the beginBackgroundTaskWithName:expirationHandler:
method at the start of your implementation and the endBackgroundTask:
method after you have processed the reply and executed the reply
block. Starting a background task ensures that your app is not
suspended before it has a chance to send its reply.
How long is the audio clip? In the code you've shared it looks like reply() would be called almost immediately, which wouldn't give the clip a chance to play. You should delay calling reply() until your audio clip has completed.
I do something very similar in my WatchKit app. When the user taps on a button I play audio from my iPhone app and my iPhone app does not need to foreground for it to work. There were two things I had to do to get it to work. The first was setting up a background task with beginBackgroundTaskWithName, which I can see you are doing. The second was to enable background audio capabilities in my background modes.
I don't remember doing anything other than those two to get it to work. I did already have background audio working in my app before I added support so it could be controlled with MPNowPlayingInfoCenter.
HI i want to implement sample application on watch kit app. i want to show some information of parent application , which is running in iphone, now i want to get data in watch kit app from preform action from button click on watch kit . i have used delegate method for background communication with extension app but Am getting same error when i print error in
[InterfaceController openParentApplication:dict reply:^(NSDictionary *replyInfo, NSError *error)
{
NSLog(#"%#",[replyInfo objectForKey:#"Key"]);
NSLog(#"error:-%#",[error description]);
}
Getting the Error ....
Error: Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]" UserInfo=0x7f8603227730 {NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]}
please suggest how can i get data in watch app from extention app.
Thanks in Advance.
In my watch app, I want to setup my mapview, so I ask my app for a location.
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
[WKInterfaceController openParentApplication:#{} reply:^(NSDictionary *replyInfo, NSError *error) {
if (replyInfo) {
[self.map setRegion:MKCoordinateRegionMake(CLLocationCoordinate2DMake([replyInfo[#"lat"] doubleValue], [replyInfo[#"lon"] doubleValue]), MKCoordinateSpanMake(0.05, 0.05))];
}
}];
}
Then I put back location from my app delegate:
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"http://www.google.com"]];
reply(#{#"lat": #"22.3175899",#"lon": #"114.2212058"});
}
I created iPhone application using sqlite db. In this sqlite db I have stories document, directory images url, and other parameters. When the user is using the "lite" version of the app, everything works fine. But when I upgrade app from "lite" version to a "paid" version, I'd like to be able to copy database and latest files in my Documents directory to the "paid" app. Assistance would be appreciated.
If the data base in Documents directory then while updating your app iOS keep the old database.
Because of app sandboxing, you can't do precisely what you want, but there are a number of approaches:
Instead of separate pro version, only have a single version of the app, which offers an In App Purchase that enables certain features. See Convert the "lite app" to "pro app" within the application. Thus, this eliminates the need to import files/settings from one app to another.
You could implement separate custom URL scheme for both free and pro versions of your app to allow them to exchange limited amount of data via a URL. Thus, the pro app would probably, on the first time it's run, check to see if the lite app is installed, and if so, request data from it:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
BOOL runAlready = [userDefaults boolForKey:kRunAlready];
if (!runAlready)
{
[userDefaults setBool:YES forKey:kRunAlready];
[userDefaults synchronize];
[self importDataFromLite];
}
}
- (void)importDataFromLite
{
NSURL *liteURL = [NSURL URLWithString:#"testapp-lite://getdata"];
// if we can open test app, then test app installed, so launch it, providing "getdata" request
if ([[UIApplication sharedApplication] canOpenURL:liteURL])
{
// Getting data from lite app
[[UIApplication sharedApplication] openURL:liteURL];
}
}
The lite version would obviously have to set up its Info.plist to register this custom URL scheme, and its app delegate would need to respond to this request for data, in turn calling a custom URL scheme of the pro app to send the data back:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
[[[UIAlertView alloc] initWithTitle:nil message:[url absoluteString] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
if ([[url absoluteString] isEqualToString:#"testapp-lite://getdata"])
{
// I'm going to create URL from local NSDictionary, but you would presumably retrieve data from your model/database
NSDictionary *dictionary = #{#"name" : #"Rob", #"age" : #"29"};
NSMutableArray *array = [NSMutableArray array];
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
[array addObject:[NSString stringWithFormat:#"%#=%#", key, [obj stringByAddingPercentEscapesForURLParameterUsingEncoding:NSUTF8StringEncoding]]];
}];
// now create the URL
NSURL *proURL = [NSURL URLWithString:[NSString stringWithFormat:#"testapp-pro://data?%#", [array componentsJoinedByString:#"&"]]];
// if we can open pro app, then then do so, providing "getdata" request
if ([[UIApplication sharedApplication] canOpenURL:proURL])
{
NSLog(#"Opening pro app");
[[UIApplication sharedApplication] openURL:proURL];
}
}
return YES;
}
In turn, the pro version could have custom URL scheme to receive the data from the lite version of the app. Thus, the pro app's handler for the data from the lite app might look like:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
NSArray *absoluteStringComponents = [[url absoluteString] componentsSeparatedByString:#"?"];
NSArray *parameters = [absoluteStringComponents[1] componentsSeparatedByString:#"&"];
for (NSString *parameter in parameters)
{
NSArray *parameterComponents = [parameter componentsSeparatedByString:#"="];
// I'm just logging the results, but you'd presumably integrate the results into your model
NSLog(#"%# is equal to %#", parameterComponents[0], [parameterComponents[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]);
}
return YES;
}
This works with modest amounts of basic data, but I would not have thought it would be well suited for exchanging image files. If interested, I can show you example of this.
In a variation of the above, you could use the UIDocumentInteractionController to import larger amounts of data, though I think this would request you to present a popover in the lite app for the user to specify to open the data file in the pro app (which seems inelegant).
You could, theoretically, store data on the cloud, possibly iCloud, DropBox, or your own server.