WCSession to check if companion app can receive message - ios

Im using WCSession to communicate between apple Watch and iOS App.
on both sides im initiating the session with:
init() {
self.session = WCSession.default
self.session.delegate = self
self.session.activate()
}
also implemented the method to activate the session when he deactivated, when session deactivated i call session.activate()
in a case where the companion app is terminated, i want the watch app to get immediate response, instead i get timeout after a minute or so.
tried to use the WCSession.isreachable but it always returns true.
is there a way for one of the session sides to check if the other side can receive the message?
thanks!

Related

How to make WCSession reachable when the watchOS app is running in background with HKWorkoutSession

My watchOS app uses workout API in order to stay running while the app goes to background. The issue is that WCSession becomes unreachable when the app is in background. However, I'm able to run my code and on some condition, it needs to send a message to the iPhone counterpart app.
The specifics of the app require that user doesn't have to interact with it - if there is a timeout, the watch app should send the message to the phone automatically.
Is this possible to achieve? Thanks.
I believe the handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) method is what you're looking for. Documentation Link
Without seeing your code I can't be sure what your current progress is, but I have used this method to update watchOS Complications in the background as the user's location changes.
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task in backgroundTasks {
if WKExtension.shared().applicationState == .background {
// Do your background work here.
if let watchComplication = task as? WKWatchConnectivityRefreshBackgroundTask {
pendingConnectivityTasks.append(watchComplication)
}
}
task.setTaskCompletedWithSnapshot(true)
}
completePendingConnectivityTasksIfNeeded()
}
As a side note I will add if your app is not an actual workout app, it will get rejected during App Review for using a HealthKit workout session.

Why can my Apple Watch OS app receive message from my iOS app only when it is active?

I am trying to build a jogging app that communicates with the Apple Watch app. When I press the "Start" button, the iOS app should signal my Watch app to start keeping track of my workout. (e.g. Showing time elapsed, measure heart rate, and etc) To make that possible, the iOS and WatchOS app should communicate. The problem with my app is that my WatchOS app can only receive a signal from my iOS app when it is on. (e.g. Watch screen is on)
This is a code from my iOS app:
#objc func startAction() {
if WCSession.isSupported() {
print("WC session is supported...")
let session = WCSession.default
session.delegate = self
session.activate()
session.sendMessage(["testWorkout":true], replyHandler: nil) { error in
print("ERROR: \(error.localizedDescription)")
}
}
}
And this is code from the other end (From the watch's end):
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
WKInterfaceDevice.current().play(.start)
print("message test workout")
}
I get a play sound and the printout message: "message test workout" when Apple Watch is on, but
when Apple Watch's screen is off, the WatchOS app receives no signal. What code can I write from iOS app's end (Or anything else I can do from WatchOS app's end) to wake up the WatchOS app?
You should not call sendMessage straight after activate - The session may not (and probably won't) be active.
You need to wait until you get the activationDidCompleteWith delegate callback and then you can attempt communication.
Before attempting to send data you should check that the session state is .active and reactivate the session if it is no longer active.

WatchKit 2.0 Send Message From Phone To Watch

I'm wanting to be able to update my Apple Watch views if a user has both the Apple Watch app open and the iPhone app open as well. I know there is a WatchKit 1 question asked here, but I want to know if I could do this using WatchConnectivity.
Within my iOS app, I send a message:
if WCSession.isSupported() {
// Set the session to default session singleton
let session = WCSession.defaultSession()
// Fire the message to watch
NSLog("send message")
session.sendMessage(["action": "messageAction"], replyHandler: nil, errorHandler: { (error) -> Void in
// Display alert
NSLog(error.description)
})
}
But I keep getting the error:
Error Domain=WCErrorDomain Code=7007 "WatchConnectivity session on paired device is not reachable." UserInfo={NSLocalizedDescription=WatchConnectivity session on paired device is not reachable.}
To send messages from the iPhone to Apple Watch, is WatchConnectivity sendMessage the correct method to use?
It's only the "correct" method if you are looking to interactively communicate with a reachable device.
But you haven't shown where you set your session delegate or activated the session:
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
You may also want to add some checks to ensure that a watch is paired (session.paired), that the watch app is installed (session.watchAppInstalled), and that the watch is actually reachable (session.reachable) before trying to interactively communicate with it, to make your app more robust.
See this guide for more details.
You could also choose to fall back on a non-interactive (background) method to queue messages for your app until it is open and can receive them.

Check if user has AppleWatch connected without prompting the watch

We are using Google Analytics, and want to know how many of our users are in possession of an AppleWatch. I have searched Stack for answers, and the recurring answer is to use this:
if WCSession.isSupported() { // check if the device support to handle an Apple Watch
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession() // activate the session
if session.paired { // Check if the iPhone is paired with the Apple Watch
// Do stuff
}
}
The problem with this is that it prompts the user to 'accept' the app on the AppleWatch. Granted, the if-statement is true wether or not the user accepts, but I don't want the user to get their hopes up, thinking the app supports AppleWatch. I simply want to know if the user has an AppleWatch, I don't want to use it. Yet.
Is there a property on the iOS-device that can be accessed to show if the user has or ever had an AppleWatch connected, without prompting the user through the Watch?
Probably you can use (Push)Notifications, since there is no need to develop a native applewatch app to receive notifications on the watch. For example i receive "whats app messages" on my watch, but it does not have any native app on the watch either.
In your watchkit extension ExtensionDelegate.m you can handle provided answers to the push message separately. Link to Apple
This would be a(nother) approach, where you have to be creative!

WCSession in Glance Interface (Watch OS 2.0)

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

Resources