I use sendMessage from iWatch (Simulator) to iOS to awake iOS app:
NSDictionary *userInfo = [[NSDictionary alloc]initWithObjectsAndKeys:#"userInfo", #"key", nil];
if ([[WCSession defaultSession] isReachable]){ //iPhone is reachable
NSLog(#"iPhone is reachable");
[[WCSession defaultSession] sendMessage:userInfo replyHandler:^(NSDictionary<NSString *,id> * _Nonnull replyMessage) {
NSLog(#"ReplyHandler run");
} errorHandler:^(NSError * _Nonnull error) {
NSLog(#"iWatch sendMessage Error: %#", error);
}
But I found that sendMessage only works if iOS app counterpart is either in Background or Foreground. If iOS app is not running at all, sendMessage cannot awake the iOS App. Both replyHandler and errorHandler were not called if App is not running in background / foreground.
This does not agree with the documentation: Calling this method from your WatchKit extension while it is active and running wakes up the corresponding iOS app in the background and makes it reachable. Calling this method from your iOS app does not wake up the corresponding WatchKit extension.
I tested on Xcode Beta 5 and the same piece of code worked even if iOS app is not running.
Anyone encounter similar issues in latest Simulator and Xcode?
Any thoughts?
Thanks
Related
I would like to have my watch app respond to the parent app on the phone being killed. When the watch app is running and the phone app is killed I get no callback from either sessionReachabilityDidChange or sessionWatchStateDidChange. Based on apple documentation:
This method is called to let the current process know that its
counterpart session’s reachability changed.
So, it seems that I should get a callback. I've set the WCSession delegate to my class on the watch. The session on the watch receives callbacks for application context. Why am i not getting a reachability callback?
Code Below..
+ (SomeClass *)sharedInstance {
static dispatch_once_t pred;
static SomeClass *shared = nil;
dispatch_once(&pred, ^{
shared = [[SomeClass alloc] init];
[Model sharedInstance].delegate = shared;
});
return shared;
}
#pragma mark - setup
- (void)initializeSession {
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
[self sync];
}
}
-(BOOL) hasValidWCSession {
return ([WCSession isSupported] && [WCSession defaultSession].isReachable);
}
#pragma mark - WCSessionDelegate
- (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *, id> *)applicationContext {
NSLog(#"application context received on watch: %#", applicationContext);
[[Model sharedInstance] process:applicationContext];
}
- (void)sessionWatchStateDidChange:(WCSession *)session {
NSLog(#"wcession state changed on watch");
}
- (void)sessionReachabilityDidChange:(WCSession *)session {
NSLog(#"wcsession reachability changed on watch");
}
The iPhone app counterpart does not need to be running in order for reachable to be true from the perspective of the Apple Watch app. If the iPhone itself is paired and reachable, sending messages using WCSession from the watch app will launch the iPhone app in the background.
Thus, I would not expect sessionReachabilityDidChange: to be called on the watch app when the iOS app is killed. This also means that your iPhone app should be prepared to be launched in the background at any time, and activate WCSession promptly, to handle incoming requests from the watch app (similar to how certain push notifications, etc., can launch an iOS app).
However, from the perspective of the iPhone app, the watch app counterpart is only considered reachable when "a paired and active Apple Watch is in range and the associated Watch app is running in the foreground" (documentation).
Also, note that sessionWatchStateDidChange: is invoked when "the value in the paired, watchAppInstalled, complicationEnabled, or watchDirectoryURL properties of the WCSession object changes".
I have an iphone app that sends a text message. And now I have started to make an Apple Watch version of this app.
When I try to import <MessageUI/MessageUI.h> it says its not found. I have been doing some reading and found out that MessageUI is not an available framework for watchOS, but I found that you can use non-supported frameworks using WatchConnectivity. My issue is all the examples I found are in swift and I am using Objective-C, my question is how do I use WatchConnectivity to use MessageUI and/or is there another way to send a text message via watchOS?
No you cannot send a text message via watchOS directly , we don't have MessageUI framework support in it. However, using WatchConnectivity send the text of message from Watch to iPhone and then on phone do the rest. But there is a limitation in the case when app is in background and message is received from watch.
On Watch
if([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
[session sendMessage:[NSDictionary dictionaryWithObject:#"message text here" forKey:#"text"]
replyHandler:^(NSDictionary *reply) {
//handle reply from iPhone app here
}
errorHandler:^(NSError *error) {
//catch any errors here
}];
}
On iPhone
- (void)session:(nonnull WCSession *)session didReceiveMessage:(nonnull NSDictionary<NSString *,id> *)message replyHandler:(nonnull void (^)(NSDictionary<NSString *,id> * __nonnull))replyHandler {
//--Message UI code here
}
Available System Technologies for WatchOS2
I have been trying to use WCSession on WatchKit 2.0 and iOS 9 but it does not seem to work.
I keep getting the same error message:
Error Domain=WCErrorDomain Code=7005 "Device is not paired." UserInfo={NSLocalizedDescription=Device is not paired., NSLocalizedRecoverySuggestion=Pair the device with a Watch.}
In the App Watch Interface I added WCSessionDelegate and in awakeWithContext: initialize the session like this:
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
In the iOS app I added WCSessionDelegate and initialized in application: didFinishLaunchingWithOptions: the same way but it is never paired on either location.
The Apple Watch IS Paired it just does not work with WCSession
And when I call the following I got the error.
[session sendMessage:userInfo replyHandler:^(NSDictionary *reply) {
NSLog(#"AW open parent: Done");
}
errorHandler:^(NSError *error) {
NSLog(#"AW open parent ERROR: %#",error.description);
}
];
Any ideas what is going on?
UPDATE:
I restarted all devices + Xcode
Apple Watch is 100% paired
Delegates are set and session is activated
iOS app does not starts at all but it works more or less with openParentApplication: reply: (The app supposed to do some heavy lifting but it terminates the app in the middle of it even though it is beginBackgroundTask is used)
Im getting the following error when sending a message from Apple Watch to device
Error Domain=WCErrorDomain Code=7012 "Message reply took too long."
UserInfo={NSLocalizedDescription=Message reply took too long.,
NSLocalizedFailureReason=Reply timeout occured.}
#import <WatchConnectivity/WatchConnectivity.h> is in both watch and main app targets, and conforms to delegate methods on both watch and device
SEND MESSAGE FROM WATCH TO DEVICE
Session confirmed as Available
Session confirmed as Reachable
NSDictionary *applicationDict = [[NSDictionary alloc] initWithObjects:#[#"SomethingHere"] forKeys:#[#"valueKey"]];
if([[WCSession defaultSession] isReachable]) {
NSLog(#"Reachable"); //<---- Console shows reachable
[[WCSession defaultSession] sendMessage:applicationDict
replyHandler:^(NSDictionary *reply) {
NSLog(#"%#",reply);
}
errorHandler:^(NSError *error) {
NSLog(#"%#",error); //<--- returns error
}];
}
DEVICE
In appdelegate didFinishLaunching
// Watch kit session
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
NSLog(#"\n\n - WatchKit Session Started - \n\n");
}
else{
NSLog(#"WatchKit Session Error");
}
Session confirmed as starting as expected
Receiving Message On Device
- (void)session:(nonnull WCSession *)session didReceiveMessage:(nonnull NSDictionary<NSString *,id> *)message replyHandler:(nonnull void (^)(NSDictionary<NSString *,id> * __nonnull))replyHandler {
NSLog(#"Data delagte");
dispatch_async(dispatch_get_main_queue(), ^{
resultFromWatch = [message objectForKey:#"resultDataValue"];
});
}
Update:
- (void) session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,id> *)message {
dispatch_async(dispatch_get_main_queue(), ^{
});
}
Stops the error message received as per ccjensen comment
Check these things:
1/ Make sure to implement the WCSessionDelegate properly on the phone side. (No idea if and/or how much you implemented so far)
In particular, make sure you implemented session(_:didReceiveMessage:replyHandler:).
2/ Make sure that you actually call the replyHandler in the WCSessionDelegate as highlighted in the doc: "You must execute the reply block as part of your implementation." WCSessionDelegate Protocol Reference
3/ Once you've checked these, make sur you run the latest version of the iPhone app before re-trying with the watch.
If these don't work, then it means your WCSessionDelegate implementation is too slow and therefore times out or you get a good old fashion network issue between the watch and the phone (unlikely in the sim, but bugs are possible).
Hope this helps.
Edit:
I missed to mention, that the counter part app must be active for it to respond. It means, the iPhone app must be at least in the background (launched once) for it to respond.
If it isn't, and after a while you will get a timeout.
Make sure you set WCSession delegate before you active the session.
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