3

I have a functional application for sharing the data between iPhone and Watch (share text) and I want to make it work even when the watch is set on background (send data from iPhone to Watch when Watch is on background). I read a lot about how to make this but nothing seemd to be ok for my application. Please add the code to make application work as I said before. Or give me some source which fit with this app. Thank you!

Code for iPhone:

    import UIKit
    import WatchConnectivity
    class ViewController: UIViewController, WCSessionDelegate {

    @IBOutlet weak var iPhoneLabel: UILabel!
    var session : WCSession!;

    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {

    }

    func sessionDidBecomeInactive(_ session: WCSession) {

    }

    func sessionDidDeactivate(_ session: WCSession) {

    }

    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        let msg = message["b"] as? String;
        self.iPhoneLabel.text = msg;

    }



    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        if(WCSession.isSupported()){
            self.session = WCSession.default;
            self.session.delegate = self;
            self.session.activate();
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    @IBAction func sendMessage(_ sender: Any) {
         session.sendMessage(["a" : "Hello"], replyHandler: nil, errorHandler: nil);
    }
}

Code for Watch:

    import WatchKit
    import Foundation
    import WatchConnectivity
    import UIKit

    class InterfaceController: WKInterfaceController, WCSessionDelegate {

    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {

    }


    @IBOutlet var WatchLabel: WKInterfaceLabel!
    var session: WCSession!;

    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        //self.label.setText(message["a"]! as? String)


        let msg = message["a"] as? String;
        WatchLabel.setText(msg);
        sendMessage();



    }

    func sendMessage(){
         session.sendMessage(["b":"goodbye"], replyHandler: nil, errorHandler: nil);
    }


    override func awake(withContext context: Any?) {
        super.awake(withContext: 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()

        if(WCSession.isSupported()){
            self.session = WCSession.default;
            self.session.delegate = self;
            self.session.activate();
        }
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }
   }

After I changed the method session.sendMessage() with session.updateApplicationContext() it only works once. Any advice?

Code for iPhone:

import UIKit

import WatchConnectivity


class ViewController: UIViewController, WCSessionDelegate {


@IBOutlet weak var iPhoneLabel: UILabel!

var session : WCSession!;



func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}

func sessionDidBecomeInactive(_ session: WCSession) {
}


 func sessionDidDeactivate(_ session: WCSession) {
}




 func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {



let msg = applicationContext["b"] as? String

//Use this to update the UI instantaneously (otherwise, takes a little while)
DispatchQueue.main.async() {
    self.iPhoneLabel.text = msg;
}

}



override func viewDidLoad() {
super.viewDidLoad()


if(WCSession.isSupported()){
    self.session = WCSession.default;
    self.session.delegate = self;
    self.session.activate();
}
}


override func didReceiveMemoryWarning() {

super.didReceiveMemoryWarning()

}


@IBAction func sendMessage(_ sender: Any) {



let applicationDict = ["a":"Hello"];
do {
    try session.updateApplicationContext(applicationDict)
} catch {
    print("error")
}
}

}

Code fore Watch:

import WatchKit

import Foundation

import WatchConnectivity

import UIKit


class InterfaceController: WKInterfaceController, WCSessionDelegate {


func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {

}


@IBOutlet var WatchLabel: WKInterfaceLabel!
var session: WCSession!;



func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
    print("Watch message received")

    let msg = applicationContext["a"] as? String
    DispatchQueue.main.async() {
        self.WatchLabel.setText(msg);
    }
    sendMessage();
}

func sendMessage(){


    print("Watch send message");
    let applicationDict = ["b":"goodbye"];
    do {
        try session.updateApplicationContext(applicationDict)
    } catch {
        print("error")
    }
}


override func awake(withContext context: Any?) {
    super.awake(withContext: 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()

    if(WCSession.isSupported()){
        self.session = WCSession.default;
        self.session.delegate = self;
        self.session.activate();
    }
}

override func didDeactivate() {
    // This method is called when watch view controller is no longer visible
    super.didDeactivate()
}

}
Muthu Sabarinathan
  • 1,198
  • 2
  • 21
  • 49

1 Answers1

3

In short, you should use the updateApplicationContext method instead of the sendMessage to be able to send data from the iPhone app even when the Watch app is in background. For further info, please continue on.

If you look at the documentation, it states that the calling session.sendMessage doesn't wake the Watch app if it is running only in the background.

Calling this method from your WatchKit extension while it is active and running wakes up the corresponding iOS app in the background and makes it reachable. Calling this method from your iOS app does not wake up the corresponding WatchKit extension. If you call this method and the counterpart is unreachable (or becomes unreachable before the message is delivered), the errorHandler block is executed with an appropriate error.

It also states that this function only works if the isReachable is true.

Use the sendMessage(:replyHandler:errorHandler:) or sendMessageData(:replyHandler:errorHandler:) method to transfer data to a reachable counterpart. These methods are intended for immediate communication between your iOS app and WatchKit extension. The isReachable property must currently be true for these methods to succeed.

For sending data that is used to update the UI, you should use the updateApplicationContext(_:) method, using which

The system sends context data when the opportunity arises, with the goal of having the data ready to use by the time the counterpart wakes up.

For this method to work, the session only needs to be activated, it doesn't need to be reachable.

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
  • You're right, but after I used this method, it doesn't do anything. Any advice? I will post the code bellow. –  Aug 03 '17 at 06:45
  • @Victor.D please don't post that as an answer, but edit your original question. Is `updateApplicationContext` throwing an error? Are you testing on Simulator or real device? – Dávid Pásztor Aug 03 '17 at 08:48
  • I don't think that would be ok to edit the post because there could be other solution that can be applied at original code. I'm not receiving any error and I'm testing it on Simulator. I've made other 2 applications with updateApplicationContext and none of them work. (App1: https://kristina.io/watchos-2-tutorial-using-application-context-to-transfer-data-watch-connectivity-2/ App2: http://www.codingexplorer.com/watch-connectivity-swift-application-context/). –  Aug 03 '17 at 09:08
  • Yes, it would be ok, that's the way how SO works. If you make some changes to your original code that still give you an error, you should edit the code in the original question and definitely don't post it as an answer. When other people look for the same question, they want to see working code in the answers. – Dávid Pásztor Aug 03 '17 at 09:16
  • You should always check if your session is activated before calling `updateApplicationContext` and you should activate the session in `ExtensionDelegate`'s `applicationDidFinishLaunching()` method, otherwise if the user doesn't open the particular `InterfaceController`, they will never receive the update. Same for the `iPhone` app. – Dávid Pásztor Aug 03 '17 at 09:18
  • Actually the application with updateDataContext only works once.Then I need to delete the app from simulator to work again. Do you know why? Is the same code. –  Aug 03 '17 at 11:52
  • What do you mean it works only once? You should be more specific when talking about unexpected behaviour. Use the debugger to check which functions are called and what path your code is executed on to see what the error might be. Also, you should test on a real device. `WatchConnectivity` uses real hardware components such as BLE, so it can work differently on the Simulator and on a real device. – Dávid Pásztor Aug 03 '17 at 12:44
  • One more thing: without seeing the exact code you are using at the moment it is hard to say anything concrete. – Dávid Pásztor Aug 03 '17 at 12:45
  • The application works only once when you send the same data, when the data change, it works somehow good. However there's a bug in updateApplicationContext when you send large data in short time, it computes wrong result and is not my fault because with session.sendMessage() method it works good all time. By the way, the code was here all the time, I also updated the post as you said. –  Aug 03 '17 at 13:41
  • The fact that you cannot send the same data several times is intended behaviour. `updateApplicationContext` was designed to send data for updating the UI and for this reason sending the same data several times makes no sense and hence the data is not sent by the framework. Sending large amount of data over a short time might prove to be problematic as well. This is again not a bug, but an implementation drawback of the framework. – Dávid Pásztor Aug 03 '17 at 13:46
  • When using the `WatchConnectivity` framework, you really need to keep in mind what data you want to send. For sending large amount of data in the background, you should use the `transferUserInfo(_:)` function rather than the `updateApplicationContext`. – Dávid Pásztor Aug 03 '17 at 13:46
  • Wish I knew this before. Is not like I have large amount of data, but I have many request in very short time and it stops working good. –  Aug 03 '17 at 13:55
  • Well, if you looked at the documentation (for which I have provided links), you could have got all this. And this is why it is important to include all relevant information when asking for debugging help. – Dávid Pásztor Aug 03 '17 at 14:00
  • 1
    I'll do better next time. –  Aug 03 '17 at 14:01
  • If you found my answer useful, please consider accepting it. – Dávid Pásztor Aug 03 '17 at 15:09
  • Hi, updateApplicationContext working fine in foreground, but when I goes to background then the session.isReachable is false. So updateApplicationContext its not working in the background. (Is updateApplicationContext is working without session delegate or session?) – Jignesh Mayani Jul 02 '21 at 08:40
  • @JigneshMayani you should check the [documentation of WCSession.isReachable](https://developer.apple.com/documentation/watchconnectivity/wcsession/1615683-isreachable), it clearly states what criteria needs to be met for `isReachable` to be `true`. – Dávid Pásztor Jul 02 '21 at 11:08