Receive Watch OS message on iOS device - ios

Hi everyone and thanks for your support in advance,
I just started working with Watch OS 2 and Objective-C and I am trying to send a message to an iPhone device when a button is tapped, I don't know if the next approach is the best but from Apple docs it seems to best feet my needs, as I need to send a request to and paired iOS device and receive some user info, I also read that this only works if the app in in foreground witch is a downside :(
Update 1
- (IBAction)didTappedButton {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
if ([session isReachable] == YES) {
NSDictionary *postDictionarry = [[NSDictionary alloc] initWithObjects:[NSArray arrayWithObject:#"retrieveAPISessionKey"] forKeys:[NSArray arrayWithObject:#"request"]];
[self.button setBackgroundColor:[UIColor blueColor]];
[session sendMessage:postDictionarry
replyHandler:^(NSDictionary<NSString *,id> * _Nonnull replyMessage) {
[self postToServer];
}
errorHandler:^(NSError * _Nonnull error) {
[self.button setBackgroundColor:[UIColor redColor]];
[self showAlertViewwithTitle:#"Oops..." andMessage:#"Something went Wrong"];
}];
}else{
[self showAlertViewwithTitle:#"Oops..." andMessage:#"Please pair with a device"];
}
}
And in the AppDelegate of I implemeted the next code in .h:
#import WatchConnectivity;
#interface AppDelegate : UIResponder <UIApplicationDelegate, UIGestureRecognizerDelegate, WCSessionDelegate>
and in the .m:
- (void)session:(nonnull WCSession *)session
didReceiveMessage:(NSDictionary<NSString *,id> *)message
replyHandler:(void(^)(NSDictionary<NSString *,id> *))replyHandler {
NSString *action = message[#"request"];
NSString *actionPerformed;
// more code here...
}
note I am testing on the simulator and have an issues in getting tap gestures and also I see a lot of spinners on the watch simulator
Update 2
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
- (IBAction)didTappedButton {
if ([[WCSession defaultSession] isReachable] == YES) {
NSDictionary *postDictionarry = [[NSDictionary alloc] initWithObjects:[NSArray arrayWithObject:#"retrieveAPISessionKey"] forKeys:[NSArray arrayWithObject:#"request"]];
[self.button setBackgroundColor:[UIColor blueColor]];
[[WCSession defaultSession] sendMessage:postDictionarry
replyHandler:^(NSDictionary<NSString *,id> * _Nonnull replyMessage) {
[self postData];
}
errorHandler:^(NSError * _Nonnull error) {
[self.button setBackgroundColor:[UIColor redColor]];
[self showAlertViewwithTitle:#"Oops..." andMessage:#"Something went Wrong"];
}];
}else{
[self showAlertViewwithTitle:#"Oops..." andMessage:#"Please pair with a device"];
}
}
- (void)postData{
//post stress signal to server
}
- (void)showAlertViewwithTitle:(NSString *)title andMessage:(NSString *)message{
WKAlertAction *act = [WKAlertAction actionWithTitle:#"OK" style:WKAlertActionStyleCancel handler:^(void){}];
NSArray *actions = #[act];
[self presentAlertControllerWithTitle:title message:message preferredStyle:WKAlertControllerStyleAlert actions:actions];
}
So, just succeede in sending the message and also pair devices but now i don't receive the sent dictionary in the iOS app.

You need to activate your WCSession in both the WatchKit App Extension and in the iPhone application. Based on the code you've shown us, it does not appear you are activating it in your iPhone application.
Add the following to your iPhone application:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([WCSession isSupported]) {
WCSession* session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
...
You should then be able to receive the message via your existing session:didReceiveMessage:replyHandler: method.

Related

Trouble sending data from iPhone to WatchOS using Application Context

I am trying to send data from my iOS app to a companion WatchOS app using WCSession. The iOS app was created with NativeScript, thus the need for Objective-C.
When running the apps on both simulators and real devices I receive the following error message:
[WC] WCSession is missing its delegate
I've been messing around with this for a few days but unable to fix this issue.
iOS Objective-C Code (This is being called in Typescript):
#import "SendToWatch.h"
#import <WatchConnectivity/WatchConnectivity.h>
#interface SendToWatch () <WCSessionDelegate>
#end
#implementation SendToWatch
- (void)sendData: (double)value {
if (WCSession.isSupported) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
NSError *error = nil;
NSDictionary *applicationDict = #{#"data":[NSString stringWithFormat:#"%0.2f", value]};
[session updateApplicationContext:applicationDict error:nil];
if (error) {
NSLog(#"%#", error.localizedDescription);
}
}
}
//MARK: - WCSessionDelegate
- (void)session:(WCSession *)session
activationDidCompleteWithState:(WCSessionActivationState)activationState
error:(NSError *)error {
}
- (void)sessionDidBecomeInactive:(WCSession *)session {
NSLog(#"Session Did Become Inactive");
}
- (void)sessionDidDeactivate:(WCSession *)session {
NSLog(#"-- Session Did Deactivate --");
[session activateSession];
}
#end
WatchOS (InterfaceController.m):
#import "InterfaceController.h"
#import <WatchConnectivity/WatchConnectivity.h>
#interface InterfaceController () <WCSessionDelegate>
#end
#implementation InterfaceController
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Creates a WCSession to allow iPhone connectivity
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
NSLog(#"-- WCSession Active --");
}
}
- (void)willActivate {
[super willActivate];
NSLog(#"-- Controller Activated --");
}
- (void)didDeactivate {
[super didDeactivate];
NSLog(#"-- Controller Deactive --");
}
//MARK: - WCSessionDelegate
// Receieves the data sent from the iPhone app
- (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary *)applicationContext {
NSString *receivedData = [applicationContext objectForKey:#"data"];
NSLog(#"-- APPLICATION CONTEXT RECEIVED --");
NSLog(#"-- Received from iOS App: %#", applicationContext);
dispatch_async(dispatch_get_main_queue(), ^{
[self.dataLabel setText:receivedData];
NSLog(#"-- DATA UPDATED --");
});
}
- (void)session:(WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState error:(NSError *)error {
}
#end
You'll need to move your session configuration and activation code
if ([WCSession isSupported]) {
WCSession* session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
to somewhere early in the iOS app's lifecycle, rather than in the sendData message.
To fix this I had to re-write the Objective-C code in typescript using the tns-platform-declarations plugin. The WCSession now has a delegate and is sending data to my companion WatchOS app.

Watch App: [WCSession defaultSession].isReachable is always retrieve 'FALSE' status on background app?

I am developing apple watch application. when app on foreground it is working fine [WCSession defaultSession].isReachable and retrieve ON status. Now my watch application is goes to background mode then problem must be create.
so how to resolve this issues? and retrieve ON status on background mode.
My code is as follows.
- (void)willActivate {
[super willActivate];
self.locationManager = [[CLLocationManager alloc] init];
[self.locationManager setDelegate:self];
[self.locationManager requestLocation];
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
if ([[WCSession defaultSession] isReachable]) {
NSLog(#"Session Reachable");
} else {
NSLog(#"Session Not Reachable");
}
if([WCSession defaultSession].iOSDeviceNeedsUnlockAfterRebootForReachability) {
WKAlertAction *action = [WKAlertAction actionWithTitle:#"OK"
style:WKAlertActionStyleDefault
handler:^{
}];
NSString *title = #"My App";
NSString *message = #"Reachability in the Watch app requires the paired iOS device to have been unlocked at least once after reboot";
[self presentAlertControllerWithTitle:title message:message preferredStyle:WKAlertControllerStyleAlert actions:#[action]];
}
if ([[WCSession defaultSession] isReachable]) {
NSString *strUserId = [[NSUserDefaults standardUserDefaults]
stringForKey:#"user_id"];
if ([strUserId isEqualToString:#""])
{
WKAlertAction *act = [WKAlertAction actionWithTitle:#"OK" style:WKAlertActionStyleCancel handler:^(void){
NSLog(#"ALERT YES ");
}];
NSArray *testing = #[act];
[self presentAlertControllerWithTitle:#"My App" message:#"You are not login" preferredStyle:WKAlertControllerStyleAlert actions:testing];
}else{
[self addTrackingdata];
[self loadPairList];
}
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(UpdateData) name:#"refreshData" object:nil];
ExtensionDelegate *del = (ExtensionDelegate *)[WKExtension sharedExtension].delegate;
if (del.strReminderTime == 0 && del.isTimerFlag) {
[self UpdateData];
isReminderFlag = NO;
del.isTimerFlag = NO;
NSTimer * CheckTimer = [NSTimer scheduledTimerWithTimeInterval:10.0f target:self selector:#selector(CheckConnectation) userInfo:nil repeats:YES];
[CheckTimer fire];
}
}
- (void)CheckConnectation
{
WCSession *session = [WCSession defaultSession];
if([WCSession isSupported]) {
session.delegate = self;
[session activateSession];
}
if([WCSession defaultSession].isReachable){
[_lblPairedStatus setText:#"connected"];
}
else
{
[_lblPairedStatus setText:#"disconnected"];
}
}
Appreciate if any suggestion or idea.
I just had a similar problem. The solution for me was to check isReachable in the didAppear function instead of willActivate.

WCSession send message is not working on the actual device but working on simulator

I'm sending a message from iPhone to Watch (WatchOS2) where there is an internal timer runs on the iPhone. For every 30 seconds, I'm sending the message from the iPhone to watch. Unfortunately, send message is working only for the first time, where my watch is receiving successfully. But, from the next time watch is not receiving any message. The whole scenario works perfectly in the simulator but not on the actual watch. Any help would be appreciated. I tried to send the message from the main thread but of no use. Any idea on where I'm doing wrong.
Thanks in advance.
this is the code I use
At watch side
//InterfaceController1.m
- (void)willActivate {
// This method is called when watch view controller is about to be visible to user
[super willActivate];
if ([WCSession isSupported]) {
self.session = [WCSession defaultSession];
self.session.delegate = self;
[self.session activateSession];
[self.session sendMessage:#{#"OpeniOS":#"YES"} replyHandler:nil errorHandler:nil];
}
}
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message replyHandler:(void(^)(NSDictionary<NSString *, id> *replyMessage))replyHandler{
//able to receive message - dictionary with IssuerNames Array from iPhone
[self setupTable:message]//I'm setting up my tableview and on click of a row from tableview I'm pushing the user to InterfaceController2.m
}
- (void)table:(WKInterfaceTable *)table didSelectRowAtIndex:(NSInteger)rowIndex{
[self.session sendMessage:#{#"AccSel":#"YES"} replyHandler:nil errorHandler:nil];
[self pushControllerWithName:#"InterfaceController2" context:[NSNumber numberWithInteger:rowIndex]];
}
//InterfaceController2.m
- (void)willActivate {
[super willActivate];
if ([WCSession isSupported]) {
_session = [WCSession defaultSession];
_session.delegate = self;
[_session activateSession];
}
}
-(void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *,id> *)message replyHandler:(void(^)(NSDictionary<NSString *, id> *replyMessage))replyHandler{
NSLog(#"%#",message);
}
At iPhone side
//ViewController1.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
if ([WCSession isSupported]) {
_session=[WCSession defaultSession];
_session.delegate=self;
[_session activateSession];
[_session sendMessage:#{#"testKey":#"testVal"} replyHandler:nil errorHandler:nil];
}
}
-(void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *,id> *)message{
if ([[message objectForKey:#"OpeniOS"] isEqualToString:#"YES"]) {
NSMutableArray *tempIssuerArray=[[NSMutableArray alloc] init];
for (OTPToken *token in self.tokenManager.tokens) {
[tempIssuerArray addObject:token.issuer];
}
if ([_session isReachable]) {
NSDictionary *temp=#{#"IssuerNames":tempIssuerArray};
[_session sendMessage:temp replyHandler:nil errorHandler:nil];
}
}
if ([[message objectForKey:#"AccSel"] isEqualToString:#"YES"]) {
OTPToken *token = [self.tokenManager.tokens objectAtIndex:[[message objectForKey:#"selIndex"] intValue]];
DisplayTokenViewController *dtvc=[self.storyboard instantiateViewControllerWithIdentifier:#"DisplayToken"];
dtvc.token=token;
dtvc.tkm=self.tokenManager;
[self.navigationController pushViewController:dtvc animated:YES];
}
}
//ViewController2.m
-(void)viewDidLoad {
[super viewDidLoad];
mySession=[WCSession defaultSession];
mySession.delegate=self;
[mySession activateSession];
[self refresh]; //this refresh method is called every 30 seconds based on a property change value
}
- (void)refresh{
NSDictionary* dict=#{#"code":#"123",#"name":#"abc"};
[mySession sendMessage:dict replyHandler:nil errorHandler:nil];
}
Actually, on the watch side, InterfaceController1.m is shown to the user at first, on a button click from InterfaceController1.m, user redirects to InterfaceController2.m. At the same time, on iPhone end I'm pushing the ViewController2.m from ViewController1.m on receiving a message from watch.
Here, the refresh method is called for only one time and after every 30 seconds, ideally refresh method should be called but not in the actual device. But everything is working perfectly in the simulator
When you use the WCSession sendMessage API with a nil replyHandler like this:
[_session sendMessage:#{#"testKey":#"testVal"} replyHandler:nil errorHandler:nil];
you need to implement the following delegate method on the receiving side:
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message {
//able to receive message - dictionary with IssuerNames Array from iPhone
[self setupTable:message]//I'm setting up my tableview and on click of a row from tableview I'm pushing the user to InterfaceController2.m
}
rather than session:didReceiveMessage:replyHandler:. So in your example above, it seems that InterfaceController1.m on the watch side should not be able to receive the message like you say it does. I'm guessing that perhaps you just made a copy paste error as you were sanitizing the code for SO.

WatchConnectivity not sending data

I'm using WatchConnectivity to send a simple dictionary from an iPhone to Apple Watch.
On the Apple Watch side, to get around the fact that contexts may not be queued when the app is opened, the last received data is saved to UserDefaults and retrieved if there is nothing in the queue when setting up my WatchKit table. I have implemented this in another WatchKit app and everything worked somewhat fine, but in this one data is never received by the Watch.
I've only tried it in the simulator because my app spins for eternity on my Watch and never loads (the loading screen looks like a WatchOs 1 screen?). The WatchConnectivity framework is included in each product (Extension and iPhone app). Thanks for your help.
Here's the iPhone code (implemented in a ViewController):
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
- (void)viewDidAppear:(BOOL)animated {
NSDictionary *toPass = [[NSDictionary alloc] initWithObjectsAndKeys:AppDelegate.profiles,#"profiles", nil];
[[WCSession defaultSession] updateApplicationContext:toPass error:nil];
NSLog(#"sent data");
[self.tableView reloadData];
}
And the Apple Watch Code:
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
self.profiles = [[NSMutableArray alloc] init];
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
[self setupTable];
}
- (void)session:(WCSession *)session didReceiveApplicationContext:(NSDictionary<NSString *,id> *)applicationContext {
NSDictionary *receieved = [[NSDictionary alloc] init];
receieved = applicationContext;
NSMutableArray *profiles = [[NSMutableArray alloc] init];
profiles = [receieved objectForKey:#"profiles"];
self.profiles = [[NSMutableArray alloc] init];
self.profiles = profiles;
NSData *arrayData = [NSKeyedArchiver archivedDataWithRootObject:self.profiles];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:arrayData forKey:#"bookmarks"];
[self setupTable];
NSLog(#"new");
}
- (void)setupTable {
...
After some setup code
if (self.profiles.count == 0) {
NSLog(#"Nothing in the queue, checking defaults");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *got = [defaults objectForKey:#"bookmarks"];
NSLog(#"Got Defaults!");
self.profiles = [[NSMutableArray alloc] init];
self.profiles = [NSKeyedUnarchiver unarchiveObjectWithData:got];
}
...
More setup code later
}
Change this line:
[[WCSession defaultSession] updateApplicationContext:toPass error:nil];
To be:
NSError *error = nil;
If (![[WCSession defaultSession] updateApplicationContext:toPass error:&error]) {
NSLog(#"error: %#", error);
}
And I bet you'll see you are getting an error returned!
Also, what type of objects does AppDelegate.profiles contain?

WCSession on Apple Watch not work properly

I have XCode 7, iPhone6 with iOS 9.1, Apple Watch with WatchOS 2.0 (now I update to 2.0.1)
I try to make communication between Watch and iPhone.
On iPhone I init my singleton
- (instancetype)init {
self = [super init];
if (self.isConnectivityAvailable) {
session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
return self;
}
- (BOOL)isConnectivityAvailable {
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"9.0")) {
return [WCSession isSupported];
} else {
return NO;
}
}
in AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//custom code
(void)[AppConnectivityHandler instance]; //start App connectivity with Apple Watch
return YES;
}
And it is all good
I process message like that
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *,id> *)message replyHandler:(void (^)(NSDictionary<NSString *,id> * _Nonnull))replyHandler {
LOG(#"receive message");
NSString *request = message[kRequestKey];
__block NSDictionary *reply = #{};
dispatch_sync(dispatch_get_main_queue(), ^{
if ([request isEqualToString:kRequestDayInfo]) {
//formirate reply dictionary
}
});
LOG(#"send reply");
replyHandler(reply);
}
On my Watch I start load when called function in my main interface controller
- (void)willActivate {
[super willActivate];
if ([WatchConnectivityHandler instance].isConnectivityAvailable) {
[[WatchConnectivityHandler instance] loadDayInfoWithCompletion:^(NSError * _Nullable error, WatchDayInfo * _Nullable dayInfo) {
//code
}];
} else {
//error
}
}
My watch singleton
+ (nonnull instancetype)instance {
static WatchConnectivityHandler *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [WatchConnectivityHandler new];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self.isConnectivityAvailable) {
session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
return self;
}
- (BOOL)isConnectivityAvailable {
return [WCSession isSupported];
}
- (void)loadDayInfoWithCompletion:(void(^ _Nonnull)( NSError * _Nullable error, WatchDayInfo * _Nullable dayInfo))completion {
[session sendMessage:#{kRequestKey : kRequestDayInfo} replyHandler:^(NSDictionary<NSString *,id> * _Nonnull replyMessage) {
NSLog(#"reply");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"%#", replyMessage);
//custom code
completion(nil, /*custom object*/);
});
} errorHandler:^(NSError * _Nonnull error) {
NSLog(#"error");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"%#", error);
completion(error, nil);
});
}];
}
So it work fine in first time and I get reply. But then I start a lot of errors like
Error Domain=WCErrorDomain Code=7007 "WatchConnectivity session on paired device is not reachable."
Error Domain=WCErrorDomain Code=7014 "Payload could not be delivered."
I test on Watch, and it is often problem to get reply form my iPhone, it wait a long. But on iPhone I test, that when it get message from Watch, it very quick send reply, but I don't see that reply on Watch.
I need update my info every time when watch start my app. What the problem? Maybe I use not properly functions?
Use transeruserinfo method instead of sendmessage and use didreceiveuserinfo method instead of didreceiveapplicationcontext.

Resources