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

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"])
}
}

Related

Issue in sending message from ios to watchos

I am trying to send a message from my iOS app to its companion Watch App. If the watch screen is ON, then everything works fine and I can see the messages. If the screen turns black, the watch is "not reachable" and my messages don't get printed.
iOS Code
// Invoking this function from viewDidLoad() of the view controller
func checkWatchConnectivityIsSupported() {
if (WCSession.isSupported()) {
print ("WC Session is supported")
let session = WCSession.default
session.delegate = self
session.activate()
}
}
// sending messages on click of a button
func sendMessageToWatch(type: String, message: String) {
print ("Sending message to watch \(type) \(message)")
// send a message to the watch if it's reachable
if (WCSession.default.isReachable) {
print ("isReachable")
// this is a meaningless message, but it's enough for our purposes
let message = [type: message]
// WCSession.default.sendMessage(message, replyHandler: nil)
WCSession.default.sendMessage(message, replyHandler: nil, errorHandler: { (err) in
print ("There was an error in sending message \(err)")
debugPrint(err)
})
} else {
// This happens when the watch screen display goes off.
print ("watch is not reachable")
}
}
WatchOS Code - InterfaceController.swift
// invoking this function from willActivate()
func checkIfWatchIsConnected() {
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
session.activate()
}
}
// implementation of delegate methods
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
print ("Message received in watch \(message)")
WKInterfaceDevice().play(.click)
let isStatusType = message["Status"] != nil
if (isStatusType) {
let text = message["Status"] as! String
statusLabel.setText(text)
return
}
}
This is the expected behaviour.
WatchKit extension. The iOS device is within range, so communication can occur and the WatchKit extension is running in the foreground, or is running with a high priority in the background (for example, during a workout session or when a complication is loading its initial timeline data).
You use isReachable and sendMessage when live messaging is required. A watch app that provides a "remote control" for the active companion iOS app or an iOS app communicating with an active workout app on the watch are examples.
In order for live messaging to work the watch does, indeed, need to be awake.
You can use the updateApplicationContext and transferUserInfo methods to transfer data to your companion app when the watch isn't active. These transfers are queued and transferred opportunistically in order to improve battery life.

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!

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)

How trigger background process from Watch on iPhone (trigger: Watch)?

I'd like to add to my Watch app functionality which send to iPhone app a Local Notification (while iPhone app is on the background or iPhone is locked).
I know how to create Local Notification itself.
What Im asking for is way, how to trigger background process (which contains also Local Notification) on iPhone by (for example) tapping on button on Apple Watch.
WKInterfaceController.openParentApplication is the official way to communicate with the iPhone. Documentation.
You pass parameters in the userInfo dictionary and retrieve results via the reply block.
On the iPhone the request is handled by appDelegate's handleWatchKitExtensionRequest method. Documentation
Code in my InterfaceController.swift:
#IBAction func btn() {
sendMessageToParentApp("Button tapped")
}
// METHODS #2:
func sendMessageToParentApp (input:String) {
let dictionary = ["message":input]
WKInterfaceController.openParentApplication(dictionary, reply: { (replyDictionary, error) -> Void in
if let castedResponseDictionary = replyDictionary as? [String:String], responseMessage = castedResponseDictionary["message"] {
println(responseMessage)
self.lbl.setText(responseMessage)
}
})
}
Next i made new method in my AppDelegate.swift:
func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: (([NSObject : AnyObject]!) -> Void)!) {
if let infoDictionary = userInfo as? [String:String], message = infoDictionary["message"] {
let response = "iPhone has seen this message." // odešle se string obsahující message (tedy ten String)
let responseDictionary = ["message":response] // tohle zase vyrobí slovník "message":String
NSNotificationCenter.defaultCenter().postNotificationName(notificationWatch, object: nil)
reply(responseDictionary)
}
}
As you can see I use Notification to get iOS app know that button has been tapped. In ViewController.swift I have Notification Observer and function which is executed every time observer catch notification that user tapped on button on watch ("notificationWatch" is global variable with notification key). Hope this will help to anybody.

Resources