Reputation: 151
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:
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:
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
}
}
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
}
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)
}
}
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)
}
}
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!
Upvotes: 0
Views: 479
Reputation: 557
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.
Upvotes: 0