WCSession issue with Xcode 7.3 - ios

Helo
Before updating Xcode to the 7.3 version I had an app with an WatchOS 2 app, The watch app would call the func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
and the iOS app would pick the call and insert the passed value. All was fine.
But since updating to Xcode 7.3 one issue i noticed, the func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
is being called twice ONLY when the iOS app is launched for the first time, if the app is running or is in the background, that function is only called once.
If I pass the values 1, 5, and 10 and the iOS app is not running, the values 1, 5, 10, 1, 5, and 10 are added. But if the app is running in any form, the values 1, 5, and 10 are added.
Any idea why?
Here is the code from the WatchOS side, I did think of that myself, but according to my tests they are only called once. I have done many tests, and this is only happening when the iOS app is launched, not when its ruling in the background.
#IBAction func ConfirmButtonPressed() {
let applicationDict = ["Amount out": self.AmountText ]// Create a dict of application data
//applicationDict = ["status": "0" ]// Create a dict of application data
WCSession.defaultSession().transferUserInfo(applicationDict)
}
Here is the iOS app code from the app delegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if (WCSession.isSupported()) {
print("xyz3")
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
..........
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
var status = false
var AmountUILabel = ""
status = false
AmountUILabel = userInfo["Amount out"] as! String
print(userInfo["Amount out"] )
let i = NSString (string: AmountUILabel ).doubleValue
let when = NSDate()
let list :[AnyObject] = controller.viewControllers!
let j = list[1].topViewController as! AllEntriesTableViewController
j.AddAmount(i , date: when, what: "---", status: status)
}

I was able to figure out the answer after a whole day of research.
I should have started the didReceiveUserInfo with dispatch_async
That fixed it and increased up the communication speed between the watch app and the iOS one.
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
dispatch_async(dispatch_get_main_queue()) {

Related

After restarting Apple Watch WCSession 'isRechable' returning false every time

I want to display data from firebase realtime database in watch app.
I have one method to fetch data from firebase in iOS parent App.
From watch app I am sending message to iOS parent app, iOS parent app will fetch data from firebase and reply to watch app with that data.
In iOS parent App activated WCSession as below,
AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Watch Connectivity setup
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
session.activate()
}
return true
}
Same way activated session in ExtensionDelegate in Watch Extension,
ExtensionDelegate.swift
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
session.activate()
}
}
Now in InterfaceController class sent message to iOS parent app and get data in reply.
InterfaceController.swift
override func awake(withContext context: Any?) {
super.awake(withContext: context)
getData()
}
func getData() {
if WCSession.default.isReachable {
session.sendMessage(["Request": "GetData"], replyHandler: { (dictReply) in
print(dictReply)
}) { (error) in
print(error)
}
} else {
let action = WKAlertAction(title: "Ok", style: .cancel) {
}
self.presentAlert(withTitle: "Error", message: "iPhone is not reachable, try reconnecting watch with iPhone and try again.", preferredStyle: .alert, actions: [action])
}
}
Handled received message in iOS parent app and sent data in reply as dictionary.
AppDelegate.swift
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
guard let request = message["Request"] as? String else {
replyHandler([:])
return
}
switch request {
case "GetData":
// Call method to get data from firebase realtime database and send response dictionary in replyHandler for demo purpose currently passed test data
replyHandler(["test" : "Hello..!!!"])
default:
replyHandler([:])
}
}
Now first time when run in device its working fine, bt after restarting apple watch opening watch app and its giving me error in alert "iPhone is not reachable, try reconnecting watch with iPhone and try again." that means WCSession.default.isReachable returning false every time. Application stuck here and just showing black screen on Apple watch.
Also If I run app from xcode then its working every time.
I am using iPhone 8 and Apple Watch series 1

iOS app going to sleep after 1 minute of communication with Watch - WatchKit

I'm implementing simple one-line communication between iOS app and watchKit with sendMessage.
The issue is - the communication is continuous.
The user presses a button on appleWatch, same action happens on Phone e.t.c.
Unfortunately, after two or three actions - the iOS app stops responding.
As advised by some on the forums - i've implemented backgroundTaskWithExpirationHandler, however, it doesn't work (doesn't give me even a promised three minute timeout).
Here's my code in the iOS app AppDelegate
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
//recieve messages from watch
print(message["b"]! as? String)
let sweetN = message["b"]! as? String
//var sweetB = message["abs"]! as? [Int : Bool]
dispatch_async(dispatch_get_main_queue(), {
let taskID = self.beginBackgroundUpdateTask()
if sweetN == "peeks"{
if WCSession.isSupported(){
let message = [ "gettingData": "datareceived" ]
session.sendMessage(message, replyHandler: { replyDict in }, errorHandler: { error in })
}
}
self.endBackgroundUpdateTask(taskID)
})
}
func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier {
return UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({})
}
func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.sharedApplication().endBackgroundTask(taskID)
}
This worked for me.
You have to set the WCSession in the app delegate like:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
replyHandler(["message": "received!"])
}
and in the apple watch: the file extension delegate.swift do the same:
override init() {
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}

Watch connectivity session issues

I am trying to send a message from a watch extension to the phone in order to update a complication.
AppDelegate.swift
var session = WCSession.defaultSession()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
if WCSession.isSupported(){
session.delegate = self
session.activateSession()
}
return true
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
replyHandler(["scheduleNames":scheduleNames, "scheduleData":scheduleData])
}
ExtensionDelegate.swift
override init(){
super.init()
if WCSession.isSupported(){
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
self.sendMessage()
}
func sendMessage(){
print("Attempting to send message")
session.sendMessage(["Sender": "Complication"], replyHandler: {
reply in
print(reply.description)
}, errorHandler: {
error in
print(error.description)
})
}
But when I run the watch simulator (and the phone app is not open), I receive
Error Domain=WCErrorDomain Code=7007 "WatchConnectivity session on paired device is not reachable."
This also comes on the physical phone and watch.
What could be causing this?
UPDATE
This only happens when I make the call from the extension. The watch app can send and receive the message fine. Also, it works when called from applicationDidFinishLaunching() but not anything else in the extension delegate.
UPDATE
I fixed the previous and now get
WatchConnectivity session has no delegate.
Thanks to this answer, I figured out the issue. Calling from the Complication (which is what I was doing) in the requestedUpdateDidBegin() executes an asynchronous method in an asynchronous method, resulting in the update function ending before the sendMessage function returns.

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.

Trigger a segue from Watch in iPhone

Is it possible to trigger a segue in the iPhone app from the Watch code? I have been looking into OS2 and it says that data can be transferred while both apps are active, but want to know if it is possible to call code within the iPhone app from the Watch code.
You can not push segue from WatchApp(Child Application) to iPhone App (Parent Application), but you can surely send data from Watch to iPhone and according to that data you can perform segue on iPhone Application.
To Send update from Apple Watch to iPhone App you have to implement openParentApplication in WatchKit Extension.
class func openParentApplication(userInfo: [NSObject : AnyObject], reply: (([NSObject : AnyObject], NSError?) -> Void)?) -> Bool
Create NSDictionary and pass that data using below code :
func updateInformationToParentApp(emergencyInfo : [NSObject : AnyObject]!){
// Call the parent application from Apple Watch
WKInterfaceController.openParentApplication(emergencyInfo) { (returnUpdate, error) -> Void in
if((error) == nil){
print("Data send successfully");
} else {
print("Error : \(error?.localizedDescription)");
}
}
}
Implement below method in AppDelegate to handle WatchApp update.
func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: ([NSObject : AnyObject]?) -> Void){
NSLog("Handler Apple Watch Event ");
watchEvent(userInfo, reply: reply);
}
func watchEvent(userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) ->Void)!) {
let dic = userInfo as NSDictionary;
NSLog("dic %#", dic);
// retrieved parameters from Apple Watch
print(userInfo["Key"])
//Perform Segue from Here according to Dictionary Info
//Pass back values to Apple Watch
var returnUpdate = Dictionary<String,AnyObject>()
let watchAppMessage = "Meesage Back To Apple Watch" as NSString;
returnUpdate["message"] = NSKeyedArchiver.archivedDataWithRootObject(watchAppMessage);
reply(returnUpdate)
}
You can see iPhone and iWatch contains different storyboards and different user interfaces, so segue from watch to iPhone is surely not possible but you can manage something like that with notifications as if in iWatch some button is pressed, you can send notification to iPhone environment and it can trigger some action.

Resources