WCSession Failing to Activate - iphone

I am having a problem with the WatchKit Connectivity Session failing to activate when I call the session.activateSession() method. This is the code I am using to set up the session.
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self // conforms to WCSessionDelegate
session.activateSession()
print("Session has been activated")
}
However, I have placed a breakpoint on the print line and when I inspect the session object, it says the sessionActivated property is still false, even after calling activateSession. I don't appear to be getting any sort of bug when I call activate session, so I assume it should have worked, but this does not seem to be the case.
Furthermore, if I try and use the sendMessage method on the session object later in my code like this -
let message = ["request": "fireLocalNotification"]
session.sendMessage(
message, replyHandler: { (replyMessage) -> Void in }) { (error) -> Void in
print(error.localizedDescription)
}
I receive an error code "The operation couldn’t be completed. (WCErrorDomain error 7004.)" which I looked up which means "WCErrorCodeSessionNotActivated." This is yet another reason why I think the activateSession method isn't calling correctly. I have even tried running the activateSession method the line directly before I send the message, but I still receive the error. If anyone could help explain what is going on, that would be wonderful, thank you! :)

You should activate the WatchConnectivity session on both the WatchKit Extension and the iOS app target. For example you might do it in the InterfaceController's
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
if WCSession.isSupported() {
let wcsession = WCSession.defaultSession()
wcsession.delegate = self
wcsession.activateSession()
wcsession.sendMessage(["update": "list"], replyHandler: { (dict) -> Void in
print("InterfaceController session response: \(dict)")
}, errorHandler: { (error) -> Void in
print("InterfaceController session error: \(error)")
})
}
}
and in the AppDelegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if WCSession.isSupported() {
let wcsession = WCSession.defaultSession()
wcsession.delegate = self
wcsession.activateSession()
}
return true
}
What I have noticed in several examples is that people tend to set a delegate only in the class which handles requests, e.g. if the watch was to send a message to the iOS app a delegate would only be set in the iOS app. This is WRONG. As the WatchConnectivity clearly states, you MUST set the delegate in both circumstances, otherwise you'll get the 7004 error.

Since "activateSession()" changed to "activate()" in Xcode 8 ,You need to add and extension for your class to delegate the session ( WCSessionDelegate ), and extend it with the function:
func session(_ session: WCSession, activationDidCompleteWith
activationState: WCSessionActivationState, error: Error?)
In order to ensure that the asynchronous method "activate" finishes successfully.
In your case:
extension YourInterfaceControllerClass : WCSessionDelegate {
func session(_ session: WCSession,
didReceiveMessage message: [String : Any],
replyHandler: #escaping ([String : Any]) -> Void)
{
//this function is mandatory and can be empty
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?)
{
// Your code to be executed after asynchronous activation is completed:
let message = ["request": "fireLocalNotification"]
session.sendMessage(
message, replyHandler: { (replyMessage) -> Void in }) { (error) -> Void in
print(error.localizedDescription)
}
//....
}
}

Are you using big number values?
NSDictionary *userInfo = #{
#"a1":#(1000000000), // 1000000000
#"a2":#(10000000000), // 1e+10
#"a3":#(100000000000), // crash!
};
[[WCSession defaultSession] transferUserInfo:userInfo];
On the above code, the value of key "a3" is dangerous, it causes Apple Watch crash.
Once you send the list, it remains in Apple Watch until reinstall the watch app.
(This crash occurs on a device, not on a simulator)

Related

Need advice on how to leverage WatchConnectivity to get one parameter from iphone in the background

I'm currently simply trying to a message from the watchKit app to the uiKit app which should respond with the data I need.
Any suggestions for how this should work? My attempt below...
I have this method on the UIKit side of my application (it's in my UIApplicationMain):
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
print("received message from watch")
syncDataBetweenDevices()
}
I want to send this message to the iphone:
func sendSignalToPhone(){
self.wcSession.sendMessage(["msg" : "Watch needs deviceId"], replyHandler: nil, errorHandler: nil)
print("Watch sent data to phone")
}

Very slow communication between apple watch and iPhone

I have wrote a very basic Xcode project that contains 3 targets:
- iOS target
- WatchKit app
- WatchKit extension
First of all, i do not understand why Xcode creates a second target (extension) for WatchKit app ? It seems that WatchKit app contains storyboard, and WatchKit extension contains swift code (controllers). Is there a particular reason for Xcode to design and split 2 targets instead of one single ?
Look at this very basic piece of code:
iOS controller:
override func viewDidLoad()
{
super.viewDidLoad()
if WCSession.isSupported()
{
let session = WCSession.default()
session.delegate = self
session.activate()
}
}
#IBAction func on_btn_tap(_ sender: Any)
{
if WCSession.isSupported()
{
let session = WCSession.default()
session.sendMessage(["mykey": "myvalue"], replyHandler: { (response) -> Void in
NSLog("OK")
}, errorHandler: { (error) -> Void in
NSLog("Error)
})
}
}
On watch extension (InterfaceController.swift):
override func awake(withContext context: Any?)
{
super.awake(withContext: context)
if WCSession.isSupported()
{
let session = WCSession.default()
session.delegate = self
session.activate()
}
}
extension InterfaceController: WCSessionDelegate
{
func session(_ session: WCSession,
activationDidCompleteWith activationState: WCSessionActivationState,
error: Error?)
{
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void)
{
self.btn.setBackgroundColor(UIColor.yellow)
}
}
As you certainly understand, i have a button on my iOS App. When i tap on this button, i send a message to Watch App and this app will change a button color.
There is a delay of about 5-6 seconds between the button tap on the iPhone and the color change. Do you know why ?
In the other communication side (watch to iPhone), it is worst (10-15 seconds)
Thanks
Since you are updating your UI you need to wrap it in a DispatchQueue, like this:
DispatchQueue.main.async {
self.btn.setBackgroundColor(UIColor.yellow)
}
These delegate callbacks are not on the main thread and you should never update your UI from any other thread than the main thread. Wrapping it like this results in much faster updating of your UI and safer code.

Cannot keep WatchOS 3 data synced with Firebase?

With the removal of WatchOS3 from being able to directly connect to the Firebase database, I am having issues keeping the watch app up to date with the data if the phone is locked or the app is not active.
I am currently having the watch request updates from the phone in the "awake", which works. However any changes/additions to the data in Firebase does not fire the update on the phone running in the background to notify the watch. If the phone app is open, communication is passed along flawlessly.
I am scoured the Apple docs, but I am not seeing what I am missing to keep the phone / Firebase connection active while in the background.
Has anyone had better luck with this or have a recommendation on a more robust solution?
Current Watch Code/Flow:
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
print("RECEIVED MESSAGE from Phone")
// REMOVED: This is where I parse json return and reloads the view data
replyHandler( [ "Pages" : "Good to Go" ] )
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print ("Watch Activation Complete")
}
override func awake(withContext context: Any?) {
super.awake(withContext: context)
let currentDate = Date()
sendMessageToPhone(command: dataCommands.sendData, value: currentDate.description)
}
func sendMessageToPhone(command: dataCommands, value: String) {
print("SENDING MESSAGE from Watch")
let data = [command.rawValue : value]
session.sendMessage(data, replyHandler: {(data: [String : Any]) -> Void in
print("Phone Got Message")
}, errorHandler: {(error ) -> Void in
print("Phone did not get message.")
self.messageErrorHandler(error: error as NSError)
})
}

Updating Glance data in watchOS2.2

I'm hoping you smart people can help me as most of the data online is out of date.
I have an iPhone app that displays financial information.
I would like to present this on a watch glance screen.
I can get the app to send the dictionary of the latest information and the glance does update live if both the Glance screen and phone app are open.
I would like to know how to use the Glance screen to ask the phone app for the latest information.
The phone app will probably be closed so it would need waking up and then asked for the current information.
I'm using swift 7 and WatchOS 2.2 and IOS 9.3
A lot of information here on Stackoverflow refers to watchOS 1 so no longer works.
I look forward to your solutions.
Look into WCSession as there are different methods for sending different types of data. This implementation is sending a dictionary.
Must setup a WCSession on both watch and phone devices. AppDelegate in didFinishLaunchingWithOptions: and I use the ExtensionDelegate in its init method. Be sure to import WatchConnectivity when using WCSession. Using the AppDelegate as a WCSessionDelegate below.
// AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Setup session on phone
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
return true
}
// WCSessionDelegate method receiving message from watch and using callback
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
// Reply with a dictionary of information based on the "message"
replyHandler(["Key": "Value"])
}
}
Setup WCSession on the watch:
// ExtensionDelegate.swift
override init() {
let session = WCSession.defaultSession()
session.activateSession()
}
Send message, consisting of a dictionary, to the phone in order to receive information in the callback:
// GlanceController.swift
WCSession.defaultSession().sendMessage(["Please give Glance data": "Value"], replyHandler:{ (response) in
// Extract data from response dictionary
}) { (error) in
// Handle error
}

watchOS2 app and iPhone app communication

In the watchOS1, we had a method “openParentApplication”. This method communicated with the phone application even when it wasn’t running in foreground or background and fetched a reply immediately. I need something similar for watchOS2. I want my watch application to communicate immediately with the phone app even if my iPhone application is not running. Methods like updateApplicationContext:error:, sendMessage:replyHandler:errorHandler: and transferUserInfo: are not helpful in this scenario.
Please can someone suggest me a better approach to achieve this?
Actually sendMessage:replyHandler:errorHandler: is doing exactly what you are asking for. As long as your watch is connected to your phone it immediately gets a response to the message. This is working when the app is in the foreground, in the background or not running at all.
Here is how you set it up:
In the WatchExtension:
Setup the session. Typically in your ExtensionDelegate:
func applicationDidFinishLaunching() {
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
And then send the message when you need something from the app:
if WCSession.defaultSession().reachable {
let messageDict = ["message": "hello iPhone!"]
WCSession.defaultSession().sendMessage(messageDict, replyHandler: { (replyDict) -> Void in
print(replyDict)
}, errorHandler: { (error) -> Void in
print(error)
}
}
In the iPhone App:
Same session setup, but this time also set the delegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
...
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
And then implement the delegate method to send the reply to the watch:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
replyHandler(["message": "Hello Watch!"])
}
This works whenever there is a connection between the Watch and the iPhone. If the app is not running, the system starts it in the background. So, basically it just works like openParentApplication(_:reply:)