iosiphoneswiftapple-watchwcsession

iPhone not receiving applicationContext from AppleWatch


I have created a program to test sending data back and forth from iPhone and AppleWatch, and visa versa. I've set it up so there is a button on the AppleWatch and a button on the iPhone. When the iPhone one is pressed, it will send data and rename the button on the AppleWatch to whatever that data String was.

I then implemented the same code for AppleWatch to iPhone but for some reason iPhone doesn't seem to receive the data. Here's the code for iPhone:

//  ViewController.swift

import UIKit
import Foundation
import WatchConnectivity

class WatchManager: UIViewController, WCSessionDelegate {

    var watchSession: WCSession? {
        didSet {
            if let session = watchSession {
                session.delegate = self
                session.activate()
            }
        }
    }

    override func viewDidLoad(){
        super.viewDidLoad()

        watchSession = WCSession.default

    }

    private func sendDict(_ dict: [String: Any]) {
        do {
            try self.watchSession?.updateApplicationContext(dict)
        } catch {
            print("Error sending dictionary \(dict) to Apple Watch!")
        }
    }

    @IBOutlet weak var transferButton: UIButton!

    @IBAction func dataTransfer(_ sender: Any) {
        sendDict(["DataKey": UUID().uuidString])
        print("sent")
    }

    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        print("Session activation did complete")
    }

    public func sessionDidBecomeInactive(_ session: WCSession) {
        print("session did become inactive")
    }

    public func sessionDidDeactivate(_ session: WCSession) {
        print("session did deactivate")
    }

    func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
        print("phone received app context: ", applicationContext)
        if let temperature = applicationContext["DataKey"] as? String {
            self.transferButton.setTitle(temperature, for: .normal)
        }

    }

}

and AppleWatch:

//  InterfaceController.swift

import WatchKit
import Foundation
import WatchConnectivity


class InterfaceController: WKInterfaceController {

    var watchSession: WCSession? {
        didSet {
            if let session = watchSession {
                session.delegate = self
                session.activate()
            }
        }
    }


    @IBOutlet weak var temperatureLabel: WKInterfaceButton!

    private func sendDict(_ dict: [String: Any]) {
        do {
            try self.watchSession?.updateApplicationContext(dict)
        } catch {
            print("Error sending dictionary \(dict) to iPhone!")
        }
    }

    @IBAction func button() {
        let urg = ["DataKey":UUID().uuidString]
        sendDict(urg)
        print("watch sent app context \(urg)")
    }

}

extension InterfaceController: WCSessionDelegate {

    #if os(iOS)
    public func sessionDidBecomeInactive(_ session: WCSession) { }
    public func sessionDidDeactivate(_ session: WCSession) {
        session.activate()
    }
    #endif

    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        print("Session activation did complete")
    }

    func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
        print("watch received app context: ", applicationContext)
        if let temperature = applicationContext["DataKey"] as? String {
            self.temperatureLabel.setTitle(temperature)
        }

    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()

        watchSession = WCSession.default

    }

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

}

I've tried changing the name of the key (didn't work), I made it so the data value is always changing (UUID().uuidString) and kept that. Other things I tried consisted of creating a label and trying to rename that instead of the button which failed, and lastly instead of renaming the button just sending some confirmation that it received data back to the apple watch, which failed.

Any help would be much appreciated, I hope it's not a silly error.


Solution

  • I think updateApplicationContextis not the right method for your needs. From the docs: 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. If you want to send data back and forth while both apps are in foreground sendMessage should work.

    In the end you may need to implement a combination of both methods. I suggest reading the following doc: https://developer.apple.com/documentation/watchconnectivity/wcsession

    Edit:

    Just to make the point with "In the end you may need to implement a combination of both methods" even clearer, I have added some sample code from one of my apps.

    The method _sendData tries to send the current data via sendMessageData, if the watch is reachable. If not it updates the application context to have the data available as some as the watch app starts.

    - (void)_sendCurrentData
    {
        if ([WCSession defaultSession].isPaired && [WCSession defaultSession].isWatchAppInstalled)
        {
            if ([WCSession defaultSession].isReachable)
            {
                // Send data to watch
                [[WCSession defaultSession] sendMessageData:self.currentData
                                               replyHandler:nil
                                               errorHandler:^(NSError * _Nonnull error) {
                                                   [self _updateApplicationContext];
                                               }];
            }
            else
            {
                [self _updateApplicationContext];
            }
        }
    }
    
    - (void)_updateApplicationContext
    {
        if ([WCSession defaultSession].isPaired && [WCSession defaultSession].isWatchAppInstalled)
        {
            NSError* error = nil;
            [[WCSession defaultSession] updateApplicationContext:@{@"data": self.currentData}
                                                           error:&error];
            if (error != nil)
            {
                NSLog(@"error while updating application context: %@", error.localizedDescription);
            }
        }
    }