Watch can not receive reply from phone with WatchConnectivity - ios

Test with iphone11(14.6) and watch series3(7.5)
In my project, I use WatchConnectivity between phone and watch to communication,
When watch send message with below code, every time run is OK.
wcSession.sendMessage(message as [String : Any], replyHandler: nil)
But when watch send message need reply, it seems error.
wcSession.sendMessage(message) { (reply) in
} errorHandler: { (error) in
}
In Phone with below code to deal with request
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
}
I debug the code, didReceiveMessage with reply data can be called, but watch can not receive the reply. So what’s my problem, Before the mobile and watch update, everything is good, every update will cause problems, how to ensure the stability of the app?Thank you very much.

Related

WatchConnectivity: didReceiveApplicationContext never gets called although context is updated

I'm stuck with the following problem: I'm able to start WCSessions on both iPhone and Watch (simulator and real devices) and can use the sendMessage method just fine. I'm aware that updateApplicationContext requires an updated dict to be sent, so I'm adding an uuid for debugging purposes:
context = ["user":id,"messageId": UUID().uuidString]
There's no error thrown when the method is called but on the watch side didReceiveApplicationContext never gets called and the receivedApplicationContext dict stays empty all the time. After reading a lot of similar code examples and the docs I can't see where I'm wrong.
I'm building with XCode 12.5 and iOS 14.5 and watchOS 7.4
Here's the iOS code dealing with the WCSession, context is set in another method and is successfully transferred with sendMessage but not with updateApplicationContext:
import WatchConnectivity
public class CDVSettings : CDVPlugin, WCSessionDelegate {
var wcSession : WCSession! = nil
var didSendMessage:Bool = false
var context: [String : Any] = [:] {
didSet {
debugPrint("context didSet:", self.context)
debugPrint("WCSession.isPaired: \(wcSession.isPaired), WCSession.isWatchAppInstalled: \(wcSession.isWatchAppInstalled)")
if wcSession.activationState == WCSessionActivationState.activated {
do {
debugPrint("updateApplicationContext is called")
self.didSendMessage=true
try wcSession.updateApplicationContext(self.context)
}
catch let error as NSError {
debugPrint(error.localizedDescription)
}
catch {}
wcSession.sendMessage(self.context, replyHandler: { reply in
print("Got reply: \(reply)")
}, errorHandler: { error in
print("error: \(error)")
})
} else {
print("activationState is not activated")
wcSession.activate()
}
}
}
public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("activationDidCompleteWith activationState:\(activationState) error:\(String(describing: error))")
}
public func sessionDidBecomeInactive(_ session: WCSession) {
}
public func sessionDidDeactivate(_ session: WCSession) {
}
#objc(pluginInitialize)
public override func pluginInitialize() {
wcSession = WCSession.default
wcSession.delegate = self
wcSession.activate()
}
[...]
}
And here's the watch part:
import WatchConnectivity
class ConnectivityRequestHandler: NSObject, ObservableObject, WCSessionDelegate {
var session = WCSession.default
override init() {
super.init()
session.delegate = self
session.activate()
debugPrint("ConnectivityRequestHandler started with session", session)
}
// MARK: WCSession Methods
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
debugPrint(error)
debugPrint("session is reachable:",session.isReachable)
debugPrint("last received application context:",session.receivedApplicationContext)
}
func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: #escaping ([String: Any]) -> Void) {
debugPrint("didReceiveMessage: \(message)")
replyHandler(["message received": Date()])
}
func session(_ session: WCSession, didReceiveMessageData messageData: Data, replyHandler: #escaping (Data) -> Void) {
debugPrint("didReceiveMessageData: \(messageData)")
}
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
debugPrint("did receive application context")
debugPrint(applicationContext)
if let id = applicationContext["user"] as? [String]{
debugPrint("\(id)")
UserDefaults.standard.set(id, forKey: "user")
}
}
}
The corresponding logs on iOS
"context didSet:" ["user": "0e4a28b5-f8a0-40a5-942d-5f13b610a93a",
"messageId": "8A78246C-91E6-48DC-B55D-4F4EBC761211"]
"WCSession.isPaired: true, WCSession.isWatchAppInstalled: true"
"updateApplicationContext is called" Got reply: ["message received":
2021-09-03 08:31:54 +0000]
and on watchOS
"ConnectivityRequestHandler started with session" <WCSession:
0x600003be0b40, hasDelegate: YES, activationState: 0> nil "session is
reachable:" true "last received application context:" [:]
"didReceiveMessage: ["user": 0e4a28b5-f8a0-40a5-942d-5f13b610a93a,
"messageId": 8A78246C-91E6-48DC-B55D-4F4EBC761211]"
What am I doing wrong? Thank you so much for your help.
For those stumbling over my question while researching, it's no real solution, but an observation that I (now) share with others (see comments above): With real devices it works as expected, but you may need to reboot them in the process. In the simulators it seems that different methods may work while others won't and that even differs from developer to developer.
In Xcode 14 this should now also work for Simulator. At least for me it seems to work without problems

WC WCSession counterpart app not installed

Making a connection between iOS and iWatch devices, xCode writes [WC] WCSession counterpart app not installed.
After a lot of research, I've found a solution, maybe it will be helpful for someone.
- Check your WatchKit Extention target.
- Uncheck "Supports Running Without iOS App Installation"
- First run iwatch simulator, than run ios simulator with checkmark
I have spent around 3-4 hr to fix this issue, it seems that somehow my Apple watch.app is missing from my Target > Frameworks so after clicking plus icon and adding it back, it doesn't report "WC WCSession counterpart app not installed" anymore
Finally, I made it. For anyone else, who has this problem:
In iOS Target: Make sure in General>Frameworks, Libraries & Embedded Contents> Add YourWatchApp.app if it's not in the list. (For the loop problem, do the next step)
In Watch Target: Go to BuildPhases>Copy Bundle Resources> and remove YouriOSApp.app from the list if exists.
You must set delegate and activate it from both WatchApp and iOSApp as follows:
self.session = WCSession.default
self.session.delegate = self
self.session.activate()
if you are using a helper class, Your class should implement NSObject as well:
class ConnectivityHelper: NSObject, WCSessionDelegate {
Make sure there is some seconds between calling activate method and sending message.
If you are using a reply handler when sending message from Watch, Then you must implement didReceiveMessage method in iOS App which has replyHandler, else replyHandler on the watch returns an error and the iOS app won't receive the message.
Check out this complete code that is working: iOS Part:
import WatchConnectivity
class PhoneConnectivity: NSObject {
override init() {
super.init()
let session = WCSession.default
session.delegate = self
session.activate()
}
}
extension PhoneConnectivity: WCSessionDelegate {
//MARK: Delegate Methodes
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func sessionDidBecomeInactive(_ session: WCSession) {
}
func sessionDidDeactivate(_ session: WCSession) {
}
// Receiver
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
//without reply handler
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
//with reply handler
}
// end Receiver
}
send message with a test string first like:
WCSession.default.sendMessage(["TEST", "TEST2"], replyHandler: { dictionary in
print(">WC> reply recieved: \(dictionary.first!.value)")
}, errorHandler: { error in
print(">WC> Error on sending Msg: \(error.localizedDescription)")
})
Most developers suggest that make session active ASAP (in iOS using didFinishLaunchingWithOptions in appDelegate of iOSApp & applicationDidFinishLaunching in WKExtensionDelegate of WatchApp)
It only worked for me by installing the Watch App from the watch settings app.
If I install the Watch App from xcode, the iOS app will give me the companion error message.

Apple Watch Kit load data on start up

How to quickly load data on Apple Watch? UserDefaults doesn't work since watchOS 2, so we can only use WCSessionDelegate, right?
Now, on Watch App start I call wcSession?.sendMessage(someThing, replyHandler: someFunc, errorHandler: otherFunc), then on iPhone app I send back some data in
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void)
And finally receive it on watch app in func session(_ session: WCSession, didReceiveMessage message: [String : Any]), but that takes like 3 seconds.
What would be better way to get data on start up?
You could try using the reply Handler function instead of initiating a new message. It should work faster.
Call this from watch:
func sendRequest() {
if session.activationState == .activated && session.isReachable {
session.sendMessage(["Watch Message" : "Request"], replyHandler: { (reply) in
// Handle reply here
}, errorHandler: { (error) in
print("***** Error Did Occur: \(error) *****")
})
}
}
Handle and Respond on Phone:
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 == "Request" {
replyHandler(["Response" : data])
}
}
}

wrong WCSessionDelegate callback function called

I use the WCSession.defaultSession().sendMessage function to send a message from the watch to the iphone, with non nil replyHandler closure
on the iphone,
func session(session: WCSession, didReceiveMessage message: [String : AnyObject])
is called and not the callback that should be called:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
and thus i cannot call replyHandler :-(
any idea why?
thanks a lot!
Edit:
code used to make the call:
WCSession.defaultSession().sendMessage(applicationDict, replyHandler: { (reply : [String : AnyObject]) -> Void in
...
},
errorHandler: { (er : NSError ) -> Void in
// Handle error
print(er.description)
});

watchconnectivity not working on device - only simulator

I have followed many tutorials online and seen how to connect the watch with the iPhone to be able to send data and have the following code to:
Send:
watchSession.sendMessage(["name":"Maz"], replyHandler: nil) { (error) -> Void in
}
Receive:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
myLabel.setText(message["name"]! as? String)
//reloadWatchTable()
}
The code works when I use the simulator but not when I'm running on my iPhone and Apple Watch.

Resources