How to access PFUser.Current() on watch app - ios

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
}
}

Related

iPhone app isn't waking in the background when WCSession's sendMessage is fired

I'm making an app for Apple Watch that needs to wake the iPhone's counterpart app which loads a site via a WKWebView, takes a snapshot, and sends the image back.
It works perfectly when the iPhone app is on-screen, intermittently when it's running in the background, but not at all when the app is completely closed.
Is there any way to get the iPhone app to wake up in the background with WCSession's sendMessage? I've read that it's meant to but I haven't been able to get it working. Is it because the iPhone app doesn't send a reply to the initial message sent by the watch (the file that the iPhone sends back has to wait for the WKWebView to finish loading, so it can't be sent back in replyHandler)? Is there a plist setting I forgot to toggle?
The current workflow of this code is as follows:
On the Apple Watch, the user taps a button which triggers the already activated WCSession's sendMessage function in ExtensionDelegate.
The iPhone app receives it using the WCSession that it activated in AppDelegate.
In didRecieve, the iPhone app feeds a URL into a WKWebView and starts loading it.
In WKWebView's didFinish function, it takes a snapshot of the site and sends it back to the watch with transferFile.
The watch receives the snapshot and passes it back to the right ViewController.
All of these steps have been tested and verified to work while both apps are on-screen, but as soon as the iPhone enters the background or has its counterpart app closed, this workflow becomes very unstable.
The relevant code is below:
After the user presses the button, the ViewController fires a notification to ExtensionDelegate with the information to transmit over WCSession.
ExtensionDelegate (sending the message):
#objc func transmit(_ notification: Notification) {
// The paired iPhone has to be connected via Bluetooth.
if let session = session, session.isReachable {
session.sendMessage(["SWTransmission": notification.userInfo as Any],
replyHandler: { replyData in
// handle reply from iPhone app here
print(replyData)
}, errorHandler: { error in
// catch any errors here
print(error)
})
} else {
// when the iPhone is not connected via Bluetooth
}
}
The iPhone app (should, but doesn't) wakes up and activates the WCSession:
fileprivate let session: WCSession? = WCSession.isSupported() ? WCSession.default : nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
session?.delegate = self
session?.activate()
webView.navigationDelegate = self
webView.scrollView.contentInsetAdjustmentBehavior = .never
return true
}
The iPhone app receives the message in AppDelegate, and activates the WKWebView. Note that there isn't a configured reply. Could this be the cause of my issue?
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
DispatchQueue.main.async { [self] in
let dictionary = message["SWTransmission"] as! [String: Any]
let link = URL(string: dictionary["URL"] as! String)!
let request = URLRequest(url: link)
webView.frame = CGRect(x: 0, y: 0, width: Int(((dictionary["width"] as! Double) * 1.5)), height: dictionary["height"] as! Int)
webView.load(request)
}
}
[Still in AppDelegate] After the site is loaded, didFinish (should) gets activated, where it takes a snapshot and sends the file back to the watch via transferFile.
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.takeSnapshot(with: nil) { [self] (image, error) in
let filename = getDocumentsDirectory().appendingPathComponent("webImage.jpg")
if let data = image!.jpegData(compressionQuality: 0.8) {
try? data.write(to: filename)
}
self.session?.transferFile(filename, metadata: nil)
}
}
The Apple Watch receives the file in ExtensionDelegate and sends it back to the relevant ViewController:
func session(_ session: WCSession, didReceive file: WCSessionFile) {
DispatchQueue.main.async { [self] in
do {
NotificationCenter.default.post(name: NSNotification.Name("openSite"), object: nil, userInfo: ["imageURL": file.fileURL] as [String: Any])
} catch {
print(error)
}
}
}
Thank you very much for your help!
I do not have experience working on WatchOS, but I took a look at the documentation for WCSession and it seems that what you are observing exactly matches the expected behaviour.
You mention
"It works perfectly when the iPhone app is on-screen, intermittently when it's running in the background, but not at all when the app is completely closed"
The Apple documentation for WCSession states
When both session objects are active, the two processes can communicate immediately by sending messages back and forth. When only one session is active, the active session may still send updates and transfer files, but those transfers happen opportunistically in the background.
These two align perfectly.
When the app is on-screen, both session objects are apparently active, and as per your observation under this scenario you see the communication happening everytime.
When the app is in background, the session object on the app side appears to be not active, and the transfer would take place 'opportunistically', which is in-line with you observation that the communication occurs intermittently.
When the the app is completely closed, it cannot be launched by the system under any circumstance as far as I know, and this is also in-line with your observation that in-this situation the communication never happens.
Unless you've already read through the WCSession documentation, I would suggest that you go through it. I see that you are checking for WCSession's isReachable property, however, another important property that is mentioned on the documentation page is activationState. It may be worth checking the value of this property before initiating the communication.

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.

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)

Resources