WatchOS 3 : Why does WCSession never activate on iPhone? - ios

I am creating a simple app that involves communication between an Apple Watch and an iPhone.
Currently using an iPhone 6s (10.3.2) with WatchOS 3.2.2
I cannot get the two to communicate, and I believe my poblem is that WCSession does not activate on the iPhone.
When the WatchOS code runs, the console prints out "activationDidCompleteWithState" as expected, indicating the WCSession has been activated(see code below).
However, on the iOS side the "activationDidCompleteWithState" method never prints to the console. If I try to send messages from the watch to the phone, they timeout and I get the following error:
[WC] -[WCSession onqueue_handleMessageCompletionWithError:withMessageID:] 109FE5D2-6218-4D67-AFD7-E72FA7E4A22E due to WCErrorCodeTransferTimedOut->IDSErrorTypeTimedOut->IDSResponseTimedOut
I believe the WCSession is just never activated on the phone. Has ayone seen this problem? I am going crazy with this...
I had previously built this with WatchOS 2, and it worked fine. Somehow, the update to WatchOS 3 has done me in. Any help would be appreciated.
iOS code in AppDelegate.m:
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([WCSession isSupported])
{
WCSession* session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
return YES;
}
- (void)session:(WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState
error:(NSError *)error
{
NSLog(#"PHONE - activationDidCompleteWithState");
}
WatchOS code in ExtensionDelegate.m:
#implementation ExtensionDelegate
- (void)applicationDidFinishLaunching
{
if ([WCSession isSupported])
{
WCSession* session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
- (void)session:(WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState
error:(NSError *)error
{
NSLog(#"activationDidCompleteWithState");
}

Ok, I figured this out.
I power cycled the iPhone which got WCSession working on the phone. Had to unpair watch, then re-pair it.
It's working now. Total nonsense...

Related

sessionReachabilityDidChange not called on watch

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".

WCSession is never paired or installed on Apple Watch

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)

ERROR: "Message reply took too long" sending message to device Watch kit OS 2

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.

transferCurrentComplicationUserInfo and ComplicationController

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)
}

'isReachable' is false when sending message from watch app to iOS app

I want to send instant message to iOS app from watch app. Implemented the following code in XCode7 beta 4 version and keeping the application in foreground in both simulators. here is the code I implemented
In watchkit interfaceController
-(void)willActivate
{
[super willActivate];
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
-(IBAction)buttonClicked
{
NSDictionary *applicationDict = [[NSDictionary alloc] initWithObjects:#[#"Hi"] forKeys:#[#"key"]];
if([[WCSession defaultSession] isReachable])
{
[[WCSession defaultSession] sendMessage:applicationDict
replyHandler:^(NSDictionary *reply) {
NSLog(#"%#",reply);
}
errorHandler:^(NSError *error) {
NSLog(#"%#",error);
}];
}
}
In iOS app class
-(void)viewDidLoad
{
[super viewDidLoad];
if ([WCSession isSupported]){
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
-(void)session:(nonnull WCSession *)session
didReceiveMessage:(nonnull NSDictionary *)message replyHandler:(nonnull void (^)(NSDictionary * __nonnull))replyHandler
{
dispatch_async(dispatch_get_main_queue(), ^{
self.testLbl.text = [message objectForKey:#"key"];
[self.view setNeedsDisplay];
});
}
Do you have to use the sendMessage APIs? I found them unreliable and unpredictable as well. I ended up using the applicationContext API's. The watch doesn't have to be reachable, but if it is, it arrives immediately, If not reachable, it gets delivered on app launch. Each time you update the application context, it overwrites the previous version, which might not be what you are looking for.
I found in an iPhone app I am currently working on that I needed to have the WCSession activation code in both the AppDelegate and the current View Controller. ...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
...
Like you, that does not marry with my understanding of what is supposed to be required, but it is what got session.reachable (Swift) to equal true
First you should check if the Watch Connectivity Framework is linked correctly, also check your code. After that try with "Reset content and settings" from both simulators, this worked for me. In case it doesn't work yet, try uninstalling and reinstalling both apps from simulators. If it still doesn't work, try with removing the watch app extension from settings on the Watch app installed on the phone. Hope this helps!

Resources