I started messing around with the Watch OS framework today and wanted to throw together a quick app, but have come to a couple questions.
I made an iOS app that just shows the current battery % as well as the state of the battery. I then wanted to show that over on the watch.
The only time the watch app will update is when I totally close the iOS app, then open it, while the watch app is active. How do I allow my watch app to be updated if I open it after the iOS app has been opened?
This kind of goes with number 2. But how do I allow the watch app to fetch info from the iOS app, after it has been in the background? As an example, lets say the iOS app has been in the background and I wanted to fetch the battery % without opening the iOS app to the foreground.
Some side notes on how I set this up -
Within the iOS app, in the viewDidLoad method, I start my session.
if ([WCSession isSupported]) {
wcSession = [WCSession defaultSession];
wcSession.delegate = self;
[wcSession activateSession];
}
Then call my method to update the actual battery % and state. Within that method, I have this which sends the info over to the watch:
NSDictionary *message = #{
#"message" : [NSString stringWithFormat:#"%#", [numberFormatter stringFromNumber:levelObj]],
#"message_2" : [NSString stringWithFormat:#"%ld",(long)[UIDevice currentDevice].batteryState],
};
[wcSession sendMessage:message replyHandler:nil errorHandler:^(NSError * _Nonnull error) {
NSLog(#"%#", error.localizedDescription);
}];
I also call this same method in the viewDidAppear, so I don't have to relaunch the app completely, to allow refreshing of the watch counterpart.
On the watch side I have the viewWillActivate method with the same activation as the iOS side as well as the method to handle what the watch app receives from the iOS side. But it will only update when I restart the iOS app fully.
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *,id> *)message {
NSLog(#"Message recieved!");
[_batteryLevelLabelW setText:message[#"message"]];
}
Also in there is the code to handle the battery state message, which is a bit long.
I hope I gave a good amount of information to help.
According to documentation:
Use the sendMessage(_:replyHandler:errorHandler:) or
sendMessageData(_:replyHandler:errorHandler:) method to
transfer data to a reachable counterpart. These methods are intended
for immediate communication between your iOS app and WatchKit
extension. The isReachable property must currently be true for these
methods to succeed.
If watchapp is not foreground, message will not be delivered since isReachable is false.
Method you should use is updateApplicationContext(_:) - it will wait till watch app will be opened at foreground and only then will be delivered.
Related
I have xamarin project. I would like to pair bluetooth button with an app and keep connection alive in suspended state. I successfully subscribed to characteristic event which represent the click in foreground state. The main use case is to handle the event in suspended state and send data to a server.
I read the documentation here, but I am having the difficulties implement restoring the CBCentralManager especially translating these methods into Xamairn.iOS.
Opt In to State Preservation and Restoration
myCentralManager =
[[CBCentralManager alloc] initWithDelegate:self queue:nil
options:#{ CBCentralManagerOptionRestoreIdentifierKey:
#"myCentralManagerIdentifier" }];
Reinstantiate Your Central and Peripheral Managers
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSArray *centralManagerIdentifiers =
launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];
...
Implement the Appropriate Restoration Delegate Method
- (void)centralManager:(CBCentralManager *)central
willRestoreState:(NSDictionary *)state {
NSArray *peripherals =
state[CBCentralManagerRestoredStatePeripheralsKey];
...
Questions:
Does app handle the events in suspended state? (technically in backgrounded)
Do I have to reconnect the device after app gets restored?
I think you can find the answer in the document.
1.Does app handle the events in suspended state? (technically in
backgrounded)
You need to enable a Core Bluetooth background execution mode in the info.plist to ensure your app keep running in background.
Also, an iOS app linked on or after iOS 10.0 must include in its Info.plist file the usage description keys for the types of data it needs to access or it will crash. InfoPlistKeyReference
You can read: Core Bluetooth Background Execution Modes
2.Do I have to reconnect the device after app gets restored?
If you gets restored here means you restart your app, I think you need to reconnect since it will lose the connection after the app is terminated.
If you gets restored here means enter foreground from background, I don't think you need to reconnect if you enabled background mode as your app is still running in the background.
I'm planning to create an iOS VoIP app(not made any iOS app before). I was reading about Callkit in IOS by which one can make his app receive phone call through iPhone native call screen.
I read Callkit api here where it is mentioned that one can know if a call is answered.
Going through this tutorial and here is the code which detects the call is answered:
-(void)reportIncomingCallWithHandle:(NSString *)handle
success:(void (^)())success
failure:(void (^)(NSError * error))failure {
CXCallUpdate *update = [self newCallUpdateWithHandle:handle];
self.callId = [NSUUID UUID];
[self.provider reportNewIncomingCallWithUUID:self.callId update:update completion:^(NSError * _Nullable error) {
if (error) {
if (failure) failure(error);
} else {
if (success) {
success();
}
}
}];
}
See the success block. So is there is a way to open my app when this success block executed? Or can I override default buttons on caller screen to open my app?
I know there is no way to open an app on receiving any kind of notification, or event trigger. So thought may be there is some way if I can do the same using Callkit
I Googled everything but found no clue regarding my above queries. Please help me if it is possible or not.
I encountered the same issue. The behavior varies depending on if the device is locked or not.
Locked: System calling screen appears. You can run the app in the background including view transitions. However, the user will only see the system calling screen although your app is kind of presented underneath the view. As the device is locked, deep links does not work as well.
Unlocked: Calling screen is the same but once the user answers the call, the app will be presented.
As you may know, we can change the icon of the button on the calling screen which opens the app, and that's the best we can do as of now.
You can not open your own VoIP app or custom UI of your App from CallKit. Use can use it in a way as Whatsapp does.
Means you can awake your app from background without using local notification. And OS will show the default incoming screen. You need not to handle anything during call. CallKit is specially made for enhancing VoIP apps by receiving calls in background, by making outgoing calls, by managing Call directory and blocking of users.
I think I know the answer to this question already, but I wanted to ask just to be thorough.
Consider the Apple Watch built-in Maps app. When you're using turn-by-turn directions, when it's time to turn left or right, the watch plays a custom haptic pattern - even though the screen is off and the app is backgrounded. Another example is while you're doing a workout - if you've set a goal, you'll get a light tap on your wrist when you get 50% there and 100% there, even if you're not looking at the watch at the time (screen off, app backgrounded).
In watchOS 2, is there any way for us 3rd party developers to have an app play a certain haptic pattern when the screen is off and the app is backgrounded? I know the playHaptic: method works while the app is active to let you play several different kinds of haptic patterns, and I know that while the app is inactive, you can have a notification come in - but the notification would only ever play the 'notification' haptic feeling, no choice in that.
You can only run custom code when you app is active. So I´m afraid you can't do this.
Here is how i'm playing haptic in background,
first of all you need to enable background mod in Capabilities for WatchExtensionand to enable:Workout Processing and Audio,Airplay.
Also you need to enable for WatchExtension HealthKit.
#import < HealthKit/HealthKit.h >
add HKWorkoutSessionDelegate
-(void)awakeWithContext:(id)context{
[super awakeWithContext:context];
HKHealthStore *cwHealthStore = [[HKHealthStore alloc] init];
cwConfiguration = [[HKWorkoutConfiguration alloc] init];
cwConfiguration.activityType = HKWorkoutActivityTypeOther;
NSError *error;
HKWorkoutSession *cwSession = [[HKWorkoutSession alloc] initWithConfiguration:cwConfiguration error:&error];
[cwSession setDelegate:self];
if (!error) {
[cwHealthStore startWorkoutSession:cwSession];
}
[self test];
}
#pragma mark WorkoutSession Delegates
- (void)workoutSession:(HKWorkoutSession *)workoutSession
didChangeToState:(HKWorkoutSessionState)toState
fromState:(HKWorkoutSessionState)fromState
date:(NSDate *)date{
NSLog(#"------>%ld", (long)toState);
}
- (void)workoutSession:(HKWorkoutSession *)workoutSession didFailWithError:(NSError *)error{
NSLog(#"%#", error);
}
And now you can play haptic in background.
-(void)test{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerTrick:) userInfo:nil repeats:true];
}
- (void)timerTrick:(NSTimer *)time {
[[WKInterfaceDevice currentDevice] playHaptic:WKHapticTypeStart];
}
Don't forghet to stop workout Session after leaving controller:
[cwHealthStore endWorkoutSession:cwSession];
Just to post an update to my own question a couple years later - in watchOS 3 workout apps were granted background execution, but no haptics (I think).
In watchOS 4, workout apps, audio recording apps and navigation apps have background execution; navigation apps can send haptics in the background. Furthermore, the "frontmost app" (app last used which still appears if wrist is raised within 2 minutes, or 8 if extended frontmost time is enabled) has some privileges for sending haptics at the conclusion of a WatchConnectivity or NSURLSession data transfer, or when a notification comes in. See the docs for details.
I watched the WWDC2015 and saw that you can develop native apps on the watch now. This opened up a lot of capabilities and I am wondering how I could send data between my iOS app and my AppleWatch app.
I saw that there is a new framework called WatchConnectivity. How can I use this and what are my options when sending data back and forth?
WatchConnectivity
First the two classes that are supposed to communicate with each other (iOS and watchOS) need to conform the <WCSessionDelegate> and #import the WatchConnectivity framework
Before you can send data you need to check if your device is able to send data
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
NSLog(#"WCSession is supported");
}
Then if you wish to use "interactive messaging" (sendMessage APIs) you will need to see if the other device is reachable first:
if ([[WCSession defaultSession] isReachable]) {
//Here is where you will send you data
}
The "background operations" APIs do not require the counterpart device to be reachable at the point in time you call the WCSession API.
You have several options when it comes to transferring data between your apps, in the Apple Documentation they are described like this:
Use the updateApplicationContext:error: method to communicate only the most recent state information to the counterpart. When the counterpart wakes, it can use this information to update its own state and remain in sync. Sending a new dictionary with this method overwrites the previous dictionary.
Use the sendMessage:replyHandler:errorHandler: or sendMessageData:replyHandler:errorHandler: method to transfer data immediately to the counterpart. These methods are intended for immediate communication when your iOS app and WatchKit extension are both active.
Use the transferUserInfo: method to transfer a dictionary of data in the background. The dictionaries you send are queued for delivery to the counterpart and transfers continue when the current app is suspended or terminated.
Use the transferFile:metadata: method to transfer files in the background. Use this method in cases where you want to send more than a simple dictionary of values. For example, use this method to send images or file-based documents.
I will give you an example how to send/receive data with Application Context
Send data:
WCSession *session = [WCSession defaultSession];
NSError *error;
[session updateApplicationContext:#{#"firstItem": #"item1", #"secondItem":[NSNumber numberWithInt:2]} error:&error];
Receive data:
- (void) session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,id> *)applicationContext {
NSLog(#"%#", applicationContext);
NSString *item1 = [applicationContext objectForKey:#"firstItem"];
int item2 = [[applicationContext objectForKey:#"secondItem"] intValue];
}
For more information about WatchConnectivity I really recommend watching the WWDC2015 session video and reading the Apple Documentation on WatchConnectivity
I was wondering if there is any other way besides MMWormhole to pass basic data between iPhone and Apple Watch. Do you know if any existing official Apple framework allows this?
It is possible.
Looking at: The WatchKit Doc's
There is a paragraph on sharing data between the watch app and the extension on the iPhone.
To quote the first paragraph.
Sharing Data with Your Containing iOS App
If your iOS app and WatchKit extension rely on the same data, use a shared app group to store that data. An app group is a secure container that multiple processes can access. Because your WatchKit extension and iOS app run in separate sandbox environments, they normally do not share files or communicate directly with one another. An app group lets the two processes share files or user defaults information between them.
From what I understand MMWormhole is handy for as close to realtime data changes between the 2 binaries. Whereas this method allows for accessing data used saved by the iPhone app that can be read by the Watch App and Vice Versa.
We can pass the data between iPhone & iWatch using groups.
Basically We can share data using the NSUserDefaults.
But for that you need to enable that see steps below:
1)open capabilities section in both your project target
2)open App Groups from that section
3)add container by click on + button with name group.test.demo
sample code to achieve that.
In your iphone app code
NSUserDefaults *myDefaults = [[NSUserDefaults alloc]initWithSuiteName:#"group.test.demo"];
[myDefaults setObject:#"tejas" forKey:#"name"];
now value "tejas" is set for key "name"
code to retrieve that
NSUserDefaults *myDefaults = [[NSUserDefaults alloc]initWithSuiteName:#"group.test.demo"];
[myDefaults objectForKey:#"name"];
best of luck :)
If you check the docs for WKInterfaceController, you'll find a method called openParentApplication:reply: that allows you to communicate with your host app in the background.
As stated above, I have used a shared app group and placed the core data files in that group. Using this technique, both the phone app and the watch can read and write the same data and all is good when they are run discretely. As each process is running in a separate sandbox, you run into the classic distributed database problem of potentially overwriting data from different sources.
To overcome this, you need to put data observers in place. I resorted to using the NSDistributedNotificationCenter to pass some custom messages between the app and the watch extension, but there may be a more elegant solution. Any ideas from others?
Use watch Connectivity.
// 1. In .m viewcontroller on phone & interface controller on iwatch
#import <WatchConnectivity/WatchConnectivity.h>
#interface IController()<WCSessionDelegate>
#end
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];}
// 2. Send Message - Phone or iWatch:
[[WCSession defaultSession] sendMessage:response
replyHandler:^(NSDictionary *reply) {
NSLog(#"%#",reply);
}
errorHandler:^(NSError *error) {
NSLog(#"%#",error);
}
];
// 3. Receive Message - Phone or iWatch
- (void)session:(WCSession *)session didReceiveMessage: (NSDictionary<NSString *, id> *)message
{
}