I have just set up WCConnectivity in my app using tutorials/sample code and I feel I have it implemented correctly. I am not using the simulator to test. For some reason, the didReceiveApplicationContext is not being called in the watch app, even though everything is set up correctly.
I've tried calling in the the Interface Controller of the WatchKit app, in the ExtensionDelegate and using NSUserDefaults to set the Interface Controller data instead.
iOS App
ViewController.m
- (void) viewDidLoad{
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
-(void) saveTrip{
NSMutableArray *currentTrips = [NSMutableArray arrayWithArray:[self.sharedDefaults objectForKey:#"UserLocations"]];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:newLocation];
[currentTrips addObject:data];
[self.sharedDefaults setObject:currentTrips forKey:#"UserLocations"];
[self.sharedDefaults synchronize];
WCSession *session = [WCSession defaultSession];
NSDictionary *applicationDict = [[NSDictionary alloc] initWithObjects:#[currentTrips] forKeys:#[#"UserLocations"]];;
[session updateApplicationContext:applicationDict error:nil];
}
Watch Extension Code
ExtensionDelegate.m
- (void)applicationDidFinishLaunching {
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
- (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,id> *)applicationContext {
self.places= [applicationContext objectForKey:#"UserLocations"];
[[NSUserDefaults standardUserDefaults] setObject:self.places forKey:#"UserLocations"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
InterfaceController.m
- (void)willActivate {
[super willActivate];
self.placesData = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:#"UserLocations"]];
[self loadData];
}
For me it was two things:
Passing invalid values in the dictionary. Can't even pass NSNull() to represent nil values. If you have data in there that can't be represented in a plist, it fails.
If the dictionary doesn't change, subsequent calls to updateApplicationContext won't trigger a corresponding call to didReceiveApplicationContext. To force an update—perhaps in debug builds—you could add a UUID to the payload, e.g.
context["force_send"] = UUID().uuidString
It might prove useful to handle any errors, so change:
[session updateApplicationContext:applicationDict error:nil];
to
NSError *error;
if (![session updateApplicationContext:applicationDict error:&error]) {
NSLog(#"updateApplicationContext failed with error %#", error);
}
If it's not working, it's probably not implemented correctly.
A few issues at first glance:
In your iOS app, you get a reference to the shared session and activate it, but don't assign that local variable to any property on your view controller. This means your local variable will go out of scope, once viewDidLoad exits. You also need to correct that in your watch extension.
In saveTrip, you again create another local session, which isn't activated and doesn't have any delegate. You need to use the first session that you set up and activated earlier.
On your watch, you save data that is received but your interface controller won't know that there is new data that it should load and display.
A few tips:
If you setup and activate the session in application:didFinishLaunchingWithOptions, it will be available app-wide (and activated much earlier in your app's lifecycle).
You should check that your session is valid, as a watch may not be paired, or the watch app may not be installed. Here's a good (Swift) tutorial that covers those cases, and also uses a session manager to make it easier to support using Watch Connectivity throughout your app.
You may want to pass less/lighter data across to the watch, than trying to deal with archiving an array of custom objects.
As an aside, what you're trying to do with NSUserDefaults is very convoluted. It's really meant to persist preferences across launches. It's not appropriate to misuse it as a way to pass your model back and forth between your extension and controller.
I had the same issue and fixed.
I forgot to add WatchConnectivity framework in watch extension.
Related
So i want to build a iOS, i am pretty new to the world of objective-c and one feature i want to implement is the ability to send a API request and do a bit of background processing while the app is not "in focus/in background". I have researched for a couple days about this BGTask API for iOS 13 and have created a projected to see if i can get "background fetch" working. I have not be able to. Im pretty sure i have everything setup correctly but i can not get background fetch functionality to trigger on my iPhone, not even once over the past couple days.
I am using a actual iOS device to test this with iOS 13.4.1
"Permitted background task scheduler identifiers" is setup properly in Info.plist
App is signed
Background processing and Background fetch is checked in Background Modes
I waited the 15 minute interval as per Apples documentation
Here is my code. All this is just a blank iOS project using objective-c. I only edited AppDelegate.m and Info.plist
AppDelegate.m
#import "AppDelegate.h"
#import <BackgroundTasks/BackgroundTasks.h>
static NSString* TaskID = #"com.myapp.task";
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:TaskID
usingQueue:nil
launchHandler:^(BGProcessingTask *task) {
[self handleAppRefreshTask:task];
}];
return YES;
}
#pragma mark - UISceneSession lifecycle
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return [[UISceneConfiguration alloc] initWithName:#"Default Configuration" sessionRole:connectingSceneSession.role];
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
-(void)handleAppRefreshTask:(BGProcessingTask *)task {
//do things with task
NSLog(#"Process started!");
task.expirationHandler = ^{
NSLog(#"WARNING: expired before finish was executed.");
};
NSString *targetUrl = #"https://webhook.site/1b274a6f-016f-4edf-8e31-4ed7058eaeac";
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setHTTPMethod:#"GET"];
[request setURL:[NSURL URLWithString:targetUrl]];
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:
^(NSData * _Nullable data,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
NSString *myString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Data received: %#", myString);
}] resume];
task.expirationHandler = ^{
NSLog(#"WARNING: expired before finish was executed.");
};
[task setTaskCompletedWithSuccess:YES];
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"Entering background");
BGProcessingTaskRequest *request = [[BGProcessingTaskRequest alloc] initWithIdentifier:TaskID];
request.requiresNetworkConnectivity = true;
request.requiresExternalPower = false;
request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:60];
#try {
[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:nil];
}
#catch(NSException *e){
NSLog(#" Unable to submit request");
}
}
#end
Is background fetch broken in iOS 13? Even clicking on the “Simulate background fetch" in Xcode debug menu does not work. It just closes the app and nothing happens. Can anybody help/give any advice?
A few observations:
The setTaskCompletedWithSuccess should be inside the network request’s completion handler. You don’t want to mark the task as complete until the request has had a chance to run and you’ve processed the result.
You are calling submitTaskRequest, but passing nil for the NSError reference. You have also wrapped that in an exception handler. But this API call doesn’t throw exceptions, but rather just passes back errors. But you have to supply it an error reference. E.g.
NSLog(#"Entering background");
BGProcessingTaskRequest *request = [[BGProcessingTaskRequest alloc] initWithIdentifier:TaskID];
request.requiresNetworkConnectivity = true;
request.requiresExternalPower = false;
request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:60];
NSError *error;
if (![[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error]) {
NSLog(#"BGTaskScheduler failed: %#", error);
}
In your code, if it failed, you would never know.
You have placed this code in applicationDidEnterBackground. I.e., are you seeing this “Entering background” message at all? The reason I ask is that if you’ve supplied a scene delegate (common if you just created a new iOS 13 app), this method won’t be called, whereas sceneDidEnterBackground will.
You said that you tried “Simulate background fetch”. But you haven’t created a background fetch request (a BGAppRefreshTask). You created a background task (a BGProcessingTask), which is a different thing. To test background processing requests, refer to Starting and Terminating Tasks During Development.
There’s an interesting question as to how you know that the fetch request was processed. You’re just using NSLog (which presumes that you’re keeping your app attached to the Xcode debugger). I would suggest testing this without the app being attached to Xcode. There are a few options:
If you can watch your server logs for requests, that works.
I personally will often put in UserNotifications (and make sure to go into settings and turn on persistent notifications so I don’t miss them).
Another approach that I’ve done is to log these events in some table in my app’s persistent storage and then have some UI within the app to fetch this data so I can confirm what happened.
I’ll often use Unified Logging so that I can watch os_log statements issued by my device from the macOS Console even when Xcode is not running. This is very useful in logging app/scene methods. See WWDC 2016 Unified Logging and Activity Tracing
Whatever you do, for things like background processing, background app refresh, etc., I will program some mechanism so that I can check to see if the requests/tasks took place, even when not attached to Xcode. Being attached to the debugger can, in some cases, affect the app lifecycle, and I want to make sure I’ve got some way to confirm what was going on without the benefit of the console.
Likely obvious, but make sure you never “force quit” the app, as that will stop background processes from taking place.
For more information, See WWDC 2019 video Advances in App Background Execution.
Hey I have made an application for my phone that can unlock/lock etc my car. Basically the iphone interface is just 4 buttons: lock, unlock, trunk, and connect. When ever I press a button a writes something to my arduino located inside of my car. I was wondering if I could "copy" these four buttons onto my apple watch. What I mean by that is that can I use openParentApplication to do that or is there some other command I could use to simulate so say button presses on my iphone when the button is clicked on my apple watch.
Code for Iphone buttons:
- (IBAction)lockCar:(UIButton *)sender;{
NSString *string = #"l";
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
if (bleShield.activePeripheral.state == CBPeripheralStateConnected) {
[bleShield write:data];
}
}
- (IBAction)trunkCar:(UIButton *)sender;{
NSString *string = #"t";
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
if (bleShield.activePeripheral.state == CBPeripheralStateConnected) {
[bleShield write:data];
}
}
- (IBAction)unlockCar:(UIButton *)sender;{
NSString *string = #"u";
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
if (bleShield.activePeripheral.state == CBPeripheralStateConnected) {
[bleShield write:data];
}
}
Code for Apple Watch so far:
- (IBAction)carConnect {
}
- (IBAction)carUnlock {
}
- (IBAction)carLock {
}
- (IBAction)carTrunk {
}
You can use WCSession to establish communication between the WatchKit extension and the companion app.
Communication channel is established as:
if ([WCSession isSupported]) {
WCSession* session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
The WCSessionDelegate must also be implemented to respond messages.
The session must be activated on each side.
Then you can send messages with sendMessage:replyHandler:errorHandler: and receive with session:didReceiveMessage:replyHandler: to execute those tasks.
I recommend reading the WatchKit Class reference and this article on Watchkit communication patterns.
I've built an app for iOS 9 and WatchOS 2. The iOS app will periodically transfer image files from the iPhone to the Watch. Sometimes, these are pushed from the app, sometimes the Watch requests (pulls) them. If pulled, I make the requests asynchronous, and use the exact same iOS code to transfer images in both cases.
About half the time (maybe 2/3), the file transfer works. The other times, it appears that nothing happens. This is the same whether I'm pushing or pulling images.
On the iOS side, I use code similar to this (session activated already):
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
if (session.reachable) {
NSData *imgData = UIImagePNGRepresentation(img);
NSURL *tempFile = [[session watchDirectoryURL] URLByAppendingPathComponent: #"camera.png"];
BOOL success = [imgData writeToFile: [tempFile path] atomically: NO];
if (success) {
NSLog(#"transferFile:metadata:");
[session transferFile: tempFile metadata: nil];
} else {
NSLog(#"will not call transferFile:metadata:");
}
} else {
NSLog(#"Camera watch client not reachable.");
}
}
On the watch extension side, I have a singleton that activates the watch session and receives the file:
- (void)session:(WCSession *)session didReceiveFile:(WCSessionFile *)file {
// pass the data file to the data listener (if any)
[self.dataListener session: session didReceiveFile: file];
}
My "data listener" converts the file to a UIImage and displays it on the UI thread. However, that's probably irrelevant, as the unsuccessful operations never get that far.
During unsuccessful transfers, session:didReceiveFile: is never called. If I inspect the iOS app's log, however, I see these messages only during the operations that fail:
Dec 26 15:10:47 hostname companionappd[74893]: (Note ) WatchKit:
application (com.mycompany.MyApp.watchkitapp), install status: 2,
message: application install success
Dec 26 15:10:47 hostname
companionappd[74893]: (Note ) WatchKit: Purging
com.mycompany.MyApp.watchkitapp from installation queue, 0 apps
remaining
What is happening here? It looks like the app is trying to reinstall the Watch app (?). When this is happening, I do not see the watch app crash/close and restart. It simply does nothing. No file received.
On the iOS side, I scale down the image to about 136x170 px, so the PNG files shouldn't be too big.
Any ideas what's going wrong?
Update:
I have posted a complete, minimal project that demonstrates the problem on Github here
I am now under the impression that this is a bug in the simulators. It seems to work more reliably on the Apple Watch hardware. Not sure if it's 100% reliable, though.
Apple bug report filed (#24023088). Will update status if there is any, and leave unsolved for any potential answers that may provide workarounds.
For me, not a single transfer was working anymore. Polling transfer.progress showed isTransferring == true, but I never got beyond 0 completed units.
I ended up:
Deleting apps on watch and iPhone
Rebooting both
Reinstalling
And it works.
This is how I managed to transfer files from phone to watch:
In order for this to work, the file must be locate in appGroupFolder, and "App Groups" must be enabled from Capabilities tab, for phone and watch.
In order to get appGroup folder use following line of code:
NSURL * myFileLocationFolder = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier: #"myGroupID"]; //something like group.bundle.projName
Once you got that use this to send message and handle response from watch:
[session sendMessage:#{#"file":myFileURL.absoluteString} replyHandler:^(NSDictionary<NSString *,id> * _Nonnull replyMessage) {
//got reply
} errorHandler:^(NSError * _Nonnull error) {
//got Error
}];
Even though WCSession *session = [WCSession defaultSession]; I have noticed that sometimes session is deallocated, so you might consider using [WCSession defaultSession]; instead.
To catch this on the phone use:
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message replyHandler:(void(^)(NSDictionary<NSString *, id> *replyMessage))replyHandler{
//message[#"file"] - addres to my file
//do stuff with it here
replyHandler(#{#"myResponse":#"responseData"}); //this call triggers replyHandler block on the watch
}
Now a if you didn't forget to implement WCSessionDelegate and use
if ([WCSession isSupported]) {
_session = [WCSession defaultSession];
_session.delegate = self;
[_session activateSession];
}
//here session is #property (strong, nonatomic) WCSession * session;
It all should work.
Made a broader answer, hopefully will reach out to more people.
I found many questions and many answers but no final example for the request:
Can anyone give a final example in Objective C what is best practice to use WCSession with an IOS app and a Watch app (WatchOS2) with more than one ViewController.
What I noticed so far are the following facts:
1.) Activate the WCSession in the parent (IOS) app at the AppDelegate:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Any other code you might have
if ([WCSession isSupported]) {
self.session = [WCSession defaultSession];
self.session.delegate = self;
[self.session activateSession];
}
}
2.) On the WatchOS2 side use <WCSessionDelegate>. But the rest is totally unclear for me! Some answers are talking from specifying keys in the passing Dictionary like:
[session updateApplicationContext:#{#"viewController1": #"item1"} error:&error];
[session updateApplicationContext:#{#"viewController2": #"item2"} error:&error];
Others are talking about retrieving the default session
WCSession* session = [WCSession defaultSession];
[session updateApplicationContext:applicationDict error:nil];
Others are talking about different queues? "It is the client's responsibility to dispatch to another queue if necessary. Dispatch back to the main."
I am totally confused. So please give an example how to use WCSession with an IOS app and a WatchOS2 App with more than one ViewController.
I need it for the following case (simplified):
In my parent app I am measuring heart rate, workout time and calories. At the Watch app 1. ViewController I will show the heart rate and the workout time at the 2. ViewController I will show the heart rate, too and the calories burned.
As far as I understand the task you just need synchronisation in a Phone -> Watch direction so in a nutshell a minimum configuration for you:
Phone:
I believe the application:didFinishLaunchingWithOptions: handler is the best place for the WCSession initialisation therefore place the following code there:
if ([WCSession isSupported]) {
// You even don't need to set a delegate because you don't need to receive messages from Watch.
// Everything that you need is just activate a session.
[[WCSession defaultSession] activateSession];
}
Then somewhere in your code that measures a heart rate for example:
NSError *updateContextError;
BOOL isContextUpdated = [[WCSession defaultSession] updateApplicationContext:#{#"heartRate": #"90"} error:&updateContextError]
if (!isContextUpdated) {
NSLog(#"Update failed with error: %#", updateContextError);
}
update:
Watch:
ExtensionDelegate.h:
#import WatchConnectivity;
#import <WatchKit/WatchKit.h>
#interface ExtensionDelegate : NSObject <WKExtensionDelegate, WCSessionDelegate>
#end
ExtensionDelegate.m:
#import "ExtensionDelegate.h"
#implementation ExtensionDelegate
- (void)applicationDidFinishLaunching {
// Session objects are always available on Apple Watch thus there is no use in calling +WCSession.isSupported method.
[WCSession defaultSession].delegate = self;
[[WCSession defaultSession] activateSession];
}
- (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,id> *)applicationContext {
NSString *heartRate = [applicationContext objectForKey:#"heartRate"];
// Compose a userInfo to pass it using postNotificationName method.
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:heartRate forKey:#"heartRate"];
// Broadcast data outside.
[[NSNotificationCenter defaultCenter] postNotificationName: #"heartRateDidUpdate" object:nil userInfo:userInfo];
}
#end
Somewhere in your Controller, let's name it XYZController1.
XYZController1:
#import "XYZController1.h"
#implementation XYZController1
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleUpdatedHeartRate:) name:#"heartRateDidUpdate" object:nil];
}
-(void)handleUpdatedHeartRate:(NSNotification *)notification {
NSDictionary* userInfo = notification.userInfo;
NSString* heartRate = userInfo[#"heartRate"];
NSLog (#"Successfully received heartRate notification!");
}
#end
Code hasn't been tested I just wrote it as is so there can be some typos.
I think the main idea now is quite clear and a transfer of remaining types of data is not that tough task.
My current WatchConnectivity architecture much more complicated but nevertheless it is based on this logic.
If you still have any questions we might move a further discussion to the chat.
Well, this is simplified version of my solution as requested by Greg Robertson. Sorry it's not in Objective-C anymore; I'm just copy-pasting from existing AppStore-approved project to make sure there will be no mistakes.
Essentially, any WatchDataProviderDelegate can hook to data provider class as that provides array holder for delegates (instead of one weak var).
Incoming WCSessionData are forwarded to all delegates using the notifyDelegates() method.
// MARK: - Data Provider Class
class WatchDataProvider: WCSessionDelegate {
// This class is singleton
static let sharedInstance = WatchDataProvider()
// Sub-Delegates we'll forward to
var delegates = [AnyObject]()
init() {
if WCSession.isSupported() {
WCSession.defaultSession().delegate = self
WCSession.defaultSession().activateSession()
WatchDataProvider.activated = true;
}
}
// MARK: - WCSessionDelegate
public func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
processIncomingMessage(userInfo)
}
public func session(session: WCSession, didReceiveApplicationContext applicationContext: [String: AnyObject]) {
processIncomingMessage(applicationContext)
}
func processIncomingMessage(dictionary: [String:AnyObject] ) {
// do something with incoming data<
notifyDelegates()
}
// MARK: - QLWatchDataProviderDelegate
public func addDelegate(delegate: AnyObject) {
if !(delegates as NSArray).containsObject(delegate) {
delegates.append(delegate)
}
}
public func removeDelegate(delegate: AnyObject) {
if (delegates as NSArray).containsObject(delegate) {
delegates.removeAtIndex((delegates as NSArray).indexOfObject(delegate))
}
}
func notifyDelegates()
{
for delegate in delegates {
if delegate.respondsToSelector("watchDataDidUpdate") {
let validDelegate = delegate as! WatchDataProviderDelegate
validDelegate.watchDataDidUpdate()
}
}
}
}
// MARK: - Watch Glance (or any view controller) listening for changes
class GlanceController: WKInterfaceController, WatchDataProviderDelegate {
// A var in Swift is strong by default
var dataProvider = WatchDataProvider.sharedInstance()
// Obj-C would be: #property (nonatomic, string) WatchDataProvider *dataProvider
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
dataProvider.addDelegate(self)
}
// WatchDataProviderDelegate
func watchDataDidUpdate() {
dispatch_async(dispatch_get_main_queue(), {
// update UI on main thread
})
}}
}
class AnyOtherClass: UIViewController, WatchDataProviderDelegate {
func viewDidLoad() {
WatchDataProvider.sharedInstance().addDelegate(self)
}
// WatchDataProviderDelegate
func watchDataDidUpdate() {
dispatch_async(dispatch_get_main_queue(), {
// update UI on main thread
})
}}
}
Doing the session management (however WCSession is singleton) in a View-Controller smells like MVC violation (and I've seen too many Watch blog posts wrong this way already).
I made an umbrella singleton class over the WCSession, that is first strongly referenced from Watch Extension Delegate to make sure it will load soon and do not get deallocated in the middle of work (e.g. when a View-Controller disappears while transferUserInfo or transferCurrentComplicationUserInfo happens in another watch thread).
Only this class then handles/holds the WCSession and decouples the session data (Model) from all the View-Controller(s) in watch app, exposing data mostly though public static class variables providing at least basic level of thread-safety.
Then this class is used both from complication controller, glance controller and other view controllers. Updates run in the background (or in backgroundFetchHandler), none of the apps (iOS/WatchOS) is required to be in foreground at all (as in case of updateApplicationContext) and the session does not necessarily have to be currently reachable.
I don't say this is ideal solution, but finally it started working once I did it this way. I'd love to hear that this is completely wrong, but since I had lots of issues before going with this approach, I'll stick to it now.
I do not give code example intentionally, as it is pretty long and I don't want anyone to blindly copy-paste it.
I found, with "try and error", a solution. It is working, but I don't know exactly why! If I send a request from the Watch to the IOS app, the delegate of that ViewController of the Watch app gets all the data from the main queue from the IOS app. I added the following code in the - (void)awakeWithContext:(id)context and the - (void)willActivate of all ViewControllers of the Watch app:
By example 0 ViewController:
[self packageAndSendMessage:#{#"request":#"Yes",#"counter":[NSString stringWithFormat:#"%i",0]}];
By example 1 ViewController1:
[self packageAndSendMessage:#{#"request":#"Yes",#"counter":[NSString stringWithFormat:#"%i",1]}];
/*
Helper function - accept Dictionary of values to send them to its phone - using sendMessage - including replay from phone
*/
-(void)packageAndSendMessage:(NSDictionary*)request
{
if(WCSession.isSupported){
WCSession* session = WCSession.defaultSession;
session.delegate = self;
[session activateSession];
if(session.reachable)
{
[session sendMessage:request
replyHandler:
^(NSDictionary<NSString *,id> * __nonnull replyMessage) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#".....replyHandler called --- %#",replyMessage);
NSDictionary* message = replyMessage;
NSString* response = message[#"response"];
[[WKInterfaceDevice currentDevice] playHaptic:WKHapticTypeSuccess];
if(response)
NSLog(#"WK InterfaceController - (void)packageAndSendMessage = %#", response);
else
NSLog(#"WK InterfaceController - (void)packageAndSendMessage = %#", response);
});
}
errorHandler:^(NSError * __nonnull error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"WK InterfaceController - (void)packageAndSendMessage = %#", error.localizedDescription);
});
}
];
}
else
{
NSLog(#"WK InterfaceController - (void)packageAndSendMessage = %#", #"Session Not reachable");
}
}
else
{
NSLog(#"WK InterfaceController - (void)packageAndSendMessage = %#", #"Session Not Supported");
}
}
is anywhere a simple tutorial, how I can use the WCSessionUserInfoTransfer to change data between my watch and iOS ?
And what must be written in the delegate of the iOS -App?
in my old programming I used:
[WKInterfaceController openParentApplication:#{#"Kommando":#"Radius"} reply:^(NSDictionary *replyInfo, NSError *error) {
if (error) {
NSLog(#"Error from parent: %#", error);
} else {
NSLog(#"Radius from parent: %#", [replyInfo objectForKey:#"Radius"]);
}
}];
The first thing you need to do is to define a WCSession. This must be defined and activated in every class that you're planning on transferring data to and receiving data from. To use WCSession, make sure it's supported, and then activate the default session as shown below.
#import <WatchConnectivity/WatchConnectivity.h>
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
From here, you can use transferUserInfo where ever you need to send data (from the watch or the iOS app):
[[WCSession defaultSession] transferUserInfo:#{#"Kommando":#"Radius"}];
On the receiving end, you would use session:didReceiveUserInfo. Note that this does NOT need to be in the app delegate anymore on the iOS app side, unlike handleWatchKitExtensionRequest. You can use this where ever you need to receive the data. Make sure to activate WCSession, as shown above, in the class where you have didReceiveUserInfo also.
- (void)session:(nonnull WCSession *)session didReceiveUserInfo:(nonnull NSDictionary<NSString *,id> *)userInfo {
NSLog(#"Radius from parent: %#", [userInfo objectForKey:#"Radius"]);
}