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.
Related
I have a Watch App that needs to have data from the iPhone App. I transfer it like so.
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
if ([[WCSession defaultSession] isReachable]) {
NSArray *keys = [NSArray arrayWithObjects: #"data", #"data1" ,nil];
NSArray *objects = [NSArray arrayWithObjects:data,data1, nil];
NSDictionary *applicationDict = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
[[WCSession defaultSession] sendMessage:applicationDict replyHandler:^(NSDictionary *replyHandler) {
} errorHandler:^(NSError *error) {
}];
}
and receive it like so.
- (void)session:(nonnull WCSession *)session didReceiveMessage:(NSDictionary<NSString *,id> *)message replyHandler:(void(^)(NSDictionary<NSString *,id> *))replyHandler {
}
However, this only works if the Apple Watch is in the foreground. Is there a way around this where Apple Watch App can receive the data without the App being in the foreground or maybe there is an alternative way of doing this like waking up the Apple Watch App before sending the data.
What you can do is to use the updateApplicationContext:error: method on WCSession object to send updated data to your watch. When your watch app wakes up, it will receive the context object with the updated data.
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've seen a similar question posted on how to send data back and forth in Swift. I'm asking the same question but in Objective-C. I've also viewed Apple's transition docs.
I work best with clear examples, rather than lecture material. So if someone has implemented this and wouldn't mind sharing, that would be much appreciated.
Here´s a link to a Q/A about WatchConnectivity: Send messages between iOS and WatchOS with WatchConnectivity in watchOS2
I will give you an example go ApplicationContext, there are 2 other messaging techniques with WatchConnectivity. Please watch WWDC2015 session video for those.
First you need to conform to the WCSessionDelegate protocol in the classes you want to send and receive data from/to. E.g both on watch and iPhone.
Basic checking before: (this is just an example, implement better than this)
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
NSLog(#"SESSION AVAIBLE");
}
//Objective-C
if ([[WCSession defaultSession] isReachable]) {
NSLog(#"SESSION REACHABLE");
}
This will send the data from the phone to the watch.
WCSession *session = [WCSession defaultSession];
NSError *error;
[session updateApplicationContext:#{#"firstItem": #"item1", #"secondItem":[NSNumber numberWithInt:2]} error:&error];
This will receive the data from the phone on the watch.
- (void) session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,id> *)applicationContext {
NSLog(#"%#", applicationContext);
item1 = [applicationContext objectForKey:#"firstItem"];
item2 = [[applicationContext objectForKey:#"secondItem"] intValue];
}
The WWDC2015 video on WatchConnectivity is really great, I recommend to check it out.
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
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.