WatchConnectivity - using sendMessage - ios

I am trying to establish connectivity between the Apple Watch (version 2.0.1) and my iPhone (running iOS 9.1) with the WatchConnectivity API in Swift.
I followed this tutorial and could not achieve messaging between the devices.
Messaging from the Apple Watch:
let applicationData = ["data":sampleData]
self.wcSession.sendMessage(applicationData, replyHandler: {(_: [String : AnyObject]) -> Void in
// handle reply from iPhone app here
}, errorHandler: {(error ) -> Void in
// catch any errors here
})
In my ViewController.swift:
// MARK: - WatchConnectivity Session
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
let sample:HKQuantitySample = (message["data"] as? HKQuantitySample)!
print("Sample messaged: \(sample)")
}
func sessionReachabilityDidChange(session: WCSession) {
print("session reachability changed: \(session.reachable)")
}
Both Watch app and the iOS app are foreground!!
I am not sure what is missing.

All of the WCSession APIs that take a dictionary as a parameter only accept dictionaries of property list types; this includes the sendMessage API you are using:
message / A dictionary of property list values that you want to send. You define the contents of the dictionary that your counterpart supports. This parameter must not be nil.
So HKSamples are not a property list type which is why this isn't working, although you are saying the error handler is not getting invoked which sounds very suspicious. Are you certain changing your code to this doesn't log anything?
self.wcSession.sendMessage(applicationData, replyHandler: {(_: [String : AnyObject]) -> Void in
// handle reply from iPhone app here
}, errorHandler: {(error ) -> Void in
print(error);
})

Related

How to access PFUser.Current() on watch app

I am using Parse and I want to be able to access the current user on my watch. There is a guide on sharing data between a host app and extensions but the function enableDataSharing(withApplicationGroupIdentifier:, containingApplication:) is marked as available. I guess I misunderstood and Watch Extensions are not considered App Extensions. Is there another way to access the current user on the Watch?
So after a lot of digging and pursuing multiple options I found out that Watch Apps are completely separate from iOS Apps and can no longer share KeyChain Access. This is probably why the built in Parse solution no longer works. Fortunately Watch Connectivity is pretty secure so I think it's safe to send the session token over from iOS and use PFUser.become to log in.
In the watch app:
guard WCSession.default.isReachable else {
print("Phone is not reachable")
return
}
WCSession.default.sendMessage(["Request": "SessionToken"],
replyHandler: (user, error) in {
guard let token = response["Response"] as? String else { return }
// there is a version of PFUser.become() with a success callback as well
PFUser.become(inBackground: token)
}
in iOS (class that conforms to WCSessionDelegate protocol):
public func session(_ session: WCSession,
didReceiveMessage message: [String: Any],
replyHandler: #escaping ([String: Any]) -> Void) {
// Respond to 'Request' message with sessionToken
if let _ = message["Request"], let sessionToken = PFUser.current()?.sessionToken {
replyHandler(["Response": sessionToken])
} else {
// Either user is not logged in, or this was not a 'Request' message
}
}

Can the watch app receive background info even if the app is in the background?

I cannot figure out how to make updateApplicationContext data arrive on the watch before the watch app is foregrounded. It seems to work only when the watch app is foregrounded.
How can the watch receive files while in the background?
This is what I've been trying to accomplish:
iOS code:
func sendDataToWatch() {
if WCSession.isSupported() {
do {
try WCSession.default.updateApplicationContext(["key":value])
} catch {
print("ERROR: \(error)")
}
}
}
Watch code:
func session(_ session: WCSession, didReceiveApplicationContext
applicationContext:[String : Any]) {
//handle data when it arrives
}
I noticed that the WatchConnectivity was provided with a handler function. Is this something I should set up to be able to handle background connectivity while the Watch App is backgrounded or not even launched?
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
// Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
for task in backgroundTasks {
// Use a switch statement to check the task type
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
// Be sure to complete the background task once you’re done.
backgroundTask.setTaskCompletedWithSnapshot(false)
default:
// make sure to complete unhandled task types
task.setTaskCompletedWithSnapshot(false)
}
}
}
According to apple you can send data from iPhone to Apple Watch using SendMessage while session is reachable.
https://developer.apple.com/documentation/watchconnectivity/wcsession/1615687-sendmessage
Calling this method from your WatchKit extension while it is active
and running wakes up the corresponding iOS app in the background and
makes it reachable.
You can use below methods to send data from the iPhone to Apple Watch
Swift 2.2
let msg = ["IPrequest":"IsLogin"]
WCSession.defaultSession().sendMessage(msg, replyHandler: { (replyDict) in
print(replyDict)
}, errorHandler: { (error) in
print(error)
})
Received dictionary using below method
Swift 2.2
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
{
dispatch_async(dispatch_get_main_queue()) { () -> Void in
print("Response:\(message)")
}
}
I have implemented above solution in one of the my project.
Hope it will help you!

How to receive a watchOS file transfer on iOS in the background

I am using transferFile and I can successfully send and receive files, but in order to complete the transfer process, I need to open up the iPhone app.
In observing other apps, it appears that they are able to receive and act upon received data in the background (and send a push notification to the user, for example).
I am wondering how they did this.
You should send a message from the watch app to the phone using the sendMessage function of watch connectivity requesting the data. This will wake up the iphone app. Then in your didreceivemessage method on the phone you should use the filetransfer function to send your files to the watch.
To clarify when a message is sent using sendMessage this wakes the iphone application up in the background to receive the message to where it can respond with a file transfer. Hope this helps
You need to send a message first before sending the file transfer. Implement something like this on your watch side
func sendActivationMessage() {
if session.activationState == .activated && session.isReachable {
session.sendMessage(["Watch Message" : "Activate"], replyHandler: {
(reply) in
if reply["Phone Message"] as! String == "Activated" {
//This is where you should implement your file transfer
}
}, errorHandler: { (error) in
print("***** Error Did Occur: \(error) *****")
})
} else {
print("***** Activation Error *****")
}
}
Then in your didreceivemessage function on the phone side implement something like this
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
if let messageFromWatch = message["Watch Message"] {
let messageData = messageFromWatch as! String
//Message From Watch to Activate Watch Connectivity Session
if messageData == "Activate" {
replyHandler(["Phone Message" : "Activated"])
}
}

AVAudioSession always returns the same outputVolume

I have an watchOS app that request the current volume from the iPhone parent app via:
session?.sendMessage(["getVolume":1], replyHandler: {
replyDict in
if let currentVolume = replyDict["currentVolume"] as? Float{
NSLog("Current volume received from phone with value: \(currentVolume)")
}
}, errorHandler: {
(error) -> Void in
NSLog("Error: :\(error)")
// iOS app failed to get message. Send it in the background
//self.session?.transferUserInfo(["getVolume":1])
})
The iPhone app handles it like this:
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
NSLog("Received a message")
if let _ = message["getVolume"] as? Int{
replyHandler(["currentVolume":AVAudioSession.sharedInstance().outputVolume])
}
else{
replyHandler([:])
}
}
This always returns the same outputVolume the phone had on the first request.
I investigated several things like if there is some kind of caching for the background request but it is always a new call that returns the new value.
Is there any workaround to get system volume with a different way or maybe a solution how to get this to work with AVAudioSession?
This isn't an issue with the WatchKit or watchOS per se, as I've experienced this in a regular iOS application.
I had to activate my app's audio session in order to observe the change in volume:
try? AVAudioSession.sharedInstance().setActive(true)

Turn transferUserInfo into sendMessage

I've currently got my data being passed from the iPhone to the Watch using transferUserInfo.
I would like to add an additional function to ask the iPhone for the same data (immediately since it could be the first time the Watch is opened) from the Watch.
So I would needsendMessage:replyHandler:errorHandler:, but I can't figure out how to use it.
(What I have with transferUserInfo:)
iPhone TableViewController.swift:
let applicationDict = ["TC" : newP.tC, "Mat" : newP.mat]
let transfer = WCSession.defaultSession().transferUserInfo(applicationDict)
Watch InterfaceController.swift:
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let tC = userInfo["TC"] as? String, let mat = userInfo["Mat"] as? String {
receivedData.append(["TC" : tC , "Mat" : mat])
ExtensionDelegate.evnts.append(Evnt(dataDictionary: ["TC" : tC , "Mat" : mat]))
doTable()
} else {
print("tC and mat are not same as dictionary value")
}
}
(What I have tried adding with sendMessage:)
iPhone TableViewController.swift:
let msg = WCSession.defaultSession().sendMessage(applicationDict, replyHandler: nil, errorHandler: nil)
Watch InterfaceController.swift:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
//handle received message
let value1 = message["TC"] as? String
let value2 = message["Mat"] as? String
//use this to present immediately on the screen
dispatch_async(dispatch_get_main_queue()) {
//self.messageLabel.setText(value)
print("ValWatch1: \(value1)")
print("ValWatch2: \(value2)")
}
//send a reply
replyHandler(["TC":"Yes"])
}
I can't tell what all I have messed up.
The code you are trying to send a message to the watch is correct
let msg = WCSession.defaultSession().sendMessage(applicationDict, replyHandler: nil, errorHandler: nil)
But are you sure the watch is paired? you may need to add this check first before sending the message
if (WCSession.defaultSession().reachable) {
// send message
}
Also make sure your watch app is running while you are trying to send the message from your parent app. If your watch app is not in the foreground, then it wouldn't receive the message sent from your iphone app.

Resources