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
Related
I am working on an app that allows video calls using an SDK that utilises webRTC on iOS.
Intended functionality is that if another call is instantiated after my app has instantiated a call, I want my app to mute audio in both directions in my call until that call has ended, in which case audio is restored.
I have encountered a problem where I want to detect other calls that make VOIP calls (for example WeChat and Facebook Messenger).
In the case of WeChat I have solved this exploiting that it interrupts the shared audio session (of AVAudioSession). The code that handles this is as follows:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector
(audioSessionInterrupted:)
...
name:AVAudioSessionInterruptionNotification object:nil];
- (void) audioSessionInterrupted:(NSNotification*)notification
{
if(notification.name == AVAudioSessionInterruptionNotification)
{
NSDictionary* userInfo = notification.userInfo;
int result = userInfo[AVAudioSessionInterruptionTypeKey];
if(result == AVAudioSessionInterruptionTypeBegan)
{
[[AVAudioSession sharedInstance] setActive:NO error:nil];
[self setAudioEnabled:NO];
}
else if (result == AVAudioSessionInterruptionTypeEnded)
{
[[AVAudioSession sharedInstance] setActive:YES error:nil];
[self setAudioEnabled:YES];
}
}
}
However, for Facebook Messenger, this method is never called. I speculate from this that WeChat may be demanding exclusive access to the shared audio session (and thus causing an interruption of the audio session with my app) whereas Facebook Messenger chooses to mix its audio, or uses a separate audio session when a call is instantiated.
My question is does there exist another way of detecting other VOIP calls, possibly using the CallKit framework? My app uses CallKit to prompt user for incoming calls, and records ingoing/outgoing calls in the iOS phone log.
I would recommend checking all current native calls. All calls that are registered through CallKit are also considered native calls and trigger a call on this object, from CoreTelephony:
CTCallCenter *callCenter = [[CTCallCenter alloc] init];
callCenter.callEventHandler = ^(CTCall* call) {
//Native call changes are triggered here
};
For detecting VOIP calls from applications that do not support CallKit, this is harder. A possibility is listening to changes to the AVAudioUnit.
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.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I want to ask a question about watch connectivity.
1) Is it possible to read data from iPhone when the iWatch app opened. I not want to wait to open iPhone app for transfering data to iWatch.
2) Is it possible to create login screen(to get user input from text fields) on iWatch
3) iWatch has device token and vendor id? How to get these infos from iWatch?
4) Is it possible to read iPhone app's database(like sql lite db on iPhone app) from iWatch application
5) How to transfer dictionary from iPhone app to iWatch app. Share any example plz.
1) Is it possible to read data from iPhone when the iWatch app opened. I not want to wait to open iPhone app for transferring data to iWatch.
YES, Using any of background methods (transferUserInfo:, transferCurrentComplicationUserInfo:, transferFile:,updateApplicationContext:infoToSend ) you can awake iPhone app and get things done. vice versa is not possible Watch app has to be Opened.
2) Is it possible to create login screen(to get user input from text fields) on iWatch
NO, Text fields are not available in WatchOS2.
3) iWatch has device token and vendor id? How to get these info from iWatch?
With watchOS 1, the vendor ID and the advertising ID were actually on the iPhone as the WatchKit extension itself ran on the iPhone.
With watchOS 2, you will need to sync the vendor ID and advertising ID from the iPhone to the Watch and use it there.
And you will need to maintain the vendor ID and advertising ID up-to-date.
4) Is it possible to read iPhone app's database(like sql lite db on iPhone app) from iWatch application
It was possible in WatchKit but with the introduction of WatchConnectivity Framework App group based common container has been restricted.I am sure for UserDefualts but not have yet tested for Files.
5) How to transfer dictionary from iPhone app to iWatch app. Share any example plz.
There are Two ways to perform these things:
Using TransferUserInfo
With this method, Watch will receive dictionary everytime, that means if Watch is inactive and iphone sends 3 Dictionary during that time period, Whenever watch will activate, it will receive all the 3 dictionary by multiple calls of delegate methods - - (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *, id> *)userInfo on watch side.
-(void)sendDictionaryToWatch:(NSDictionary*)infoToSend{
if([WCSession isSupported]){
WCSession *aDefaultSession = [WCSession defaultSession];
aDefaultSession.delegate = self;
if([aDefaultSession isPaired] && [aDefaultSession isWatchAppInstalled]){
[aDefaultSession activateSession];
[aDefaultSession transferUserInfo:infoToSend];
}
}
}
Using updateApplicationContext:error:
In this case, Device will send the latest Context to Watch on activation. That means let say If you have sent three Info back to back then When Watch is Activated it will receive only latest one, not previous ones in delegate method - -(void)session:(WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,id> *)applicationContext.
-(void)sendDictionaryToWatch:(NSDictionary*)infoToSend{
if([WCSession isSupported]){
WCSession *aDefaultSession = [WCSession defaultSession];
aDefaultSession.delegate = self;
if([aDefaultSession isPaired] && [aDefaultSession isWatchAppInstalled]){
[aDefaultSession activateSession];
[aDefaultSession updateApplicationContext:infoToSend error:nil];
}
}
}
I'm trying to use messaging (part of the new WatchConnectivity introduced in watchOS 2.0) in my glance. In my glance controller I have.
-(void)willActivate {
[super willActivate];
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
This works in the main interface albeit takes a good few seconds to actually become reachable in the simulator. I monitor the reachability by checking sessionReachabilityDidChange:. However only in my glance interface it never becomes reachable. Without it being reachable I cannot retrieve data from the phone. Has anyone run into this? Maybe it's just a simulator issue. I'm using xCode 7 Beta 5.
Thanks!
There is only one shared WCSession object and it has only one delegate, so setting it twice in the app's init/willActivate and then in the Glance init/willActivate will cause issues.
The viable way is to set it in WKExtensionDelegate's init method
class ExtensionDelegate: WKExtensionDelegate, WCSessionDelegate{
let TAG : String = "ExtensionDelegate: "
let session = WCSession.defaultSession()
override init () {
super.init()
println("\(TAG) - init")
println("\(TAG)Setting delegate and Activating WCSession.defaultSession()...")
session.delegate = self
session.activateSession()
}
.
.
.
}
Before your session will have became reachable, you can communicate with updateApplicationContext:.
So you can check in your iOS side that the watch is reachable, and if not then use updateApplicationContext: to set some application context data which will be available for the watch when the user start using your watchkit app. Or even in the glance you can use this context data.
When your willActivate method will get called, you can set the WCSession delegate, and activate the session. When the session will be activated, then it will call your session:didReceiveApplicationContext: callback with the previously set appcontext data. After this, you can use the sendMessage because in this point your watch is reachable.
Note that the updateApplicationContext method is not called on the main thread.
In the other direction, you can wake up your iOS app in the background with using the sendMessage method from the watch. This means you have to setup the WCSession in your appdelegate on the iOS side.
Reachable state needs that both the iOS app and the watch app (or glance) are running.
For more info check the WWDC Session 713: Introducing Watch Connectivity
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
{
}