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!
Related
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...
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.
In my app, I have to send information from the watch InterfaceController to the phone HomeViewController. But, when I run my code, the information only works once. For it to work again, I have to delete the Apple Watch app and reinstall it.
InterfaceController.m:
#import "InterfaceController.h"
#import <WatchConnectivity/WatchConnectivity.h>
#interface InterfaceController() <WCSessionDelegate>
#property (strong, nonatomic) WCSession *session;
#end
#implementation InterfaceController
-(instancetype)init {
self = [super init];
if (self) {
if ([WCSession isSupported]) {
self.session = [WCSession defaultSession];
self.session.delegate = self;
[self.session activateSession];
}
}
return self;
}
-(void)sendText:(NSString *)text {
NSDictionary *applicationDict = #{#"text":text};
[self.session updateApplicationContext:applicationDict error:nil];
}
- (IBAction)ButtonPressed {
[self sendText:#"Hello World"];
}
HomeViewController.m:
#import "HomeViewController.h"
#import <WatchConnectivity/WatchConnectivity.h>
#interface HomeViewController ()<WCSessionDelegate>
#end
#implementation HomeViewController
#synthesize TextLabel;
- (void)viewDidLoad {
[super viewDidLoad];
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
- (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,id> *)applicationContext {
NSString *text = [applicationContext objectForKey:#"text"];
dispatch_async(dispatch_get_main_queue(), ^{
[TextLabel setText:text];
});
}
As mentioned, the iOS label only changes to "Hello World" once. After I relaunch the iOS app, and its text label no longer says "Hello World," I cannot manage to get the watch to change the iOS text label back to "Hello World" again.
Is this a problem with communication between the watch and the iPhone, or is it a problem with the code?
It's a problem with the code, based on the intent of updateApplicationContext:
you should use this method to communicate state changes or to deliver data that is updated frequently
In your case, you're trying to resend an unchanged application context from the watch to the phone.
Since there is no change from the previous application context, and the phone won't receive anything different than what it previously received, there's no reason for the watch to (re)transmit anything, so it doesn't.
This is an optimization that Apple designed into Watch Connectivity.
How can you resolve this?
You could redesign your app to eliminate needing to retransmit the same data.
If your app must retransmit the same information a second time, you have to change your approach:
You can add additional data (such as a UUID or timestamp) to the application context, to ensure that the update you're sending is not identical to the previous application context you sent.
Use different WCSession functionality, such as sendMessage, which would let you resend identical data a second time.
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'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.