wrong WCSessionDelegate callback function called - watchos-2

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

Related

Watch can not receive reply from phone with WatchConnectivity

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.

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

Error: "Message reply took too long" - WCSession Watch OS2

So I am using Watch Connectivity to request an array from the iPhone to the Watch.
The idea was to sendMessage from the watch, and the iPhone will reply with the array within the didReceiveMessage method.
However the iPhone does not seem to be responding, I thought the iPhone would open the application when I send the message from the Watch. I have tried even opening the application when I sendMessage but still no luck. When I wait long enough I get the following error message:
Error Domain=WCErrorDomain Code=7012 "Message reply took too long."
UserInfo={NSLocalizedDescription=Message reply took too long.,
NSLocalizedFailureReason=Reply timeout occured.}
Does anybody know where I may be going wrong ?
Apple Watch
import WatchKit
import Foundation
import CoreData
import WatchConnectivity
class BookmarkedInterfaceController: WKInterfaceController, WCSessionDelegate {
var session : WCSession!
var objects: [AnyObject]!
#IBOutlet var table: WKInterfaceTable!
override func willActivate() {
super.willActivate()
//Check if session is supported and Activate
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
sendMessageToIphone()
}
func sendMessageToIphone() {
if WCSession.defaultSession().reachable {
print("WCSession is reachabe")
let messageDict = ["Request": "iPhone Can You Give Me The Array"]
WCSession.defaultSession().sendMessage(messageDict, replyHandler: { (replyDict) -> Void in
print(replyDict)
}, errorHandler: { (error) -> Void in
print(error)
})
}
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
//recieving message from iphone
print("recieved message from iphone \(message)")
objects.append(message["Array"]!)
print("Objects array = \(objects)")
}
The console outputs
WCSession is reachabe
Array nil
iPhone App Delegate
import UIKit
import CoreData
import WatchConnectivity
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
var window: UIWindow?
var session : WCSession!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//Check if session is supported and Activate
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
print("did recieve message from Watch")
let applicationData = ["Array":["One", "Two", "Three"]]
replyHandler(applicationData)
}
Nothing from the iPhone is being executed. Even when I manually open the app.
If you want the reply to the message the watch sent to contain the requested data, you should change your code to the following:
Watch
func sendMessageToIphone() {
if WCSession.defaultSession().reachable {
print("WCSession is reachabe")
let messageDict = ["Request": "iPhone Can You Give Me The Array"]
WCSession.defaultSession().sendMessage(messageDict, replyHandler: { (replyDict) -> Void in
print("Array \(replyDict["array"])")
}, errorHandler: { (error) -> Void in
print(error)
})
}
}
Phone
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
print("did recieve message from Watch")
let applicationData = ["Array":["One", "Two", "Three"]]
//If identifier from recievedMessage is for Objects
replyHandler(applicationData)
}
And separately, the reason why the sendMessage from the phone is not received by the watch is because you've implemented the wrong delegate method for the sendMessage invocation you are using.
If you call sendMessage with a nil replyHandler then this delegate method will be invoked on the receiving side:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject])
If you call sendMessage with a non-nil replyHandler then this delegate method will be invoked on the receiving side:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
You must activate the session before sending the message .Also you must set the delegate before you activate the session because you may lose some pending messages.
iphone side :
import UIKit
import WatchConnectivity
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
var window: UIWindow?
var session : WCSession!
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
print("did recieve message from Watch")
let applicationData = ["Array":["One", "Two", "Three"]]
//If identifier from recievedMessage is for Objects
session.sendMessage(applicationData, replyHandler: { reply in
print("Got reply: \(reply)")
}, errorHandler: { error in
print("error: \(error)")
})
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
return true
}
}
iWatch extension InterfaceController
You must activate the session in the interface controller willactivate method.
import WatchKit
import Foundation
import WatchConnectivity
class InterfaceController: WKInterfaceController, WCSessionDelegate {
var session : WCSession!
var objects: [AnyObject]!
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
objects = []
sendMessageToIphone()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
func sendMessageToIphone() {
if WCSession.defaultSession().reachable{
print("WCSession is reachabe")
let messageDict = ["Request": "iPhone Can You Give Me The Array"]
WCSession.defaultSession().sendMessage(messageDict, replyHandler: { (replyDict) -> Void in
print(replyDict)
}, errorHandler: { (error) -> Void in
print(error)
})
}
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
//recieving message from iphone
print("recieved message from iphone \(message)")
objects.append(message["Array"]!)
print("Objects array = \(objects)")
}
}
Note. Run the iphone application first. Then Run the extension and keep the iphone app in foreground.

Call a public method of a WKInterfaceController from ExtensionDelegate class- WatchKit

I am calling the
`[[WCSession defaultSession] updateApplicationContext:message error:error]`
method which is triggered in my
`-(void)session:(WCSession *)session didReceiveApplicationContext:(NSDictionary<NSString *,id> *)applicationContext`
method in ExtensionDelegate. But from here I want to call a public method in a WKInterfaceController to update my UI. I don't want to reload the root controllers as this particular controller is not the root controller. Is it possible to call any public method from ExtensionDelegate. Can I call
-(void)session:(WCSession *)session didReceiveApplicationContext:(NSDictionary<NSString *,id> *)applicationContext
from somewhere within Interface controller instead of ExtensionDelegate?
You can use WCSession's
public func sendMessage(message: [String : AnyObject], replyHandler: (([String : AnyObject]) -> Void)?, errorHandler: ((NSError) -> Void)?)
this will invoke WCSessionDelegate's
public func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
method in delegate class. This can be used from both app and extension for instantaneous message transfer. Read more

Pass a swift closure to an objective-C function which takes a block as parameter

I have a function in objective-C as following
- (void) fetchChannelListForWatch:(void (^)(NSDictionary *))callback
I want to pass a swift callback closure into this like this:
fetchChannelListForWatch(replyHandler)
where replyHandler is a closure of type
replyHandler: ([String : AnyObject]) -> Void)
and I get the error:
Cannot invoke 'fetchChannelListForWatch' with an argument list of type '(([String : AnyObject]) -> Void)'
The replyHandler is coming from WatchConnectivity delegate
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
so I cannot change the type of replyHandler.
How can I pass my swift closure with parameter
replyHandler: [String: AnyObject] -> ()
into an objective-C function that takes a block with parameter
- (void) fetchChannelListForWatch:(void (^)(NSDictionary *))callback
Your help is much appreciated!
I think this could be a shortcut to your problem:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void){
let objCObject = ObjectiveCClass()
objCObject.fetchChannelListForWatch { (dict) -> Void in
replyHandler(dict as! [String : AnyObject]?)
}
}
The bridged type for NSDictionary is
[NSObject: AnyObject]
In your case you need to update your replyHandler to
replyHandler: ([NSObject : AnyObject]) -> Void)
Here is the relevant documentation https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html

Resources