Passing data to Apple Watch app

18,766

Solution 1

This applies to OS 1 only. See below for better answers.

I got it working using your method. I guess there's a couple of things you can check:

1) Are you synchronising the defaults after you set the value:

defaults?.synchronize();
NSLog("%@ ", defaults?.dictionaryRepresentation())

2) Have you enabled the App Group in both your app and your extension?

App Group capability for App Target App Group capability for Watch Extension Target

3) Are you using the correctly named app group when constructing the NSDefaults? For example, I use:

NSUserDefaults(suiteName: "group.com.brindysoft.MyWatch");

Once all that's set up I run the app, set the value in the defaults, then run the glance target which reads the value from the default and that seems to work!

enter image description here

  1. Still stuck? check your app groups in your apple account

Solution 2

The accepted answer applies to apple watch os 1. See NSUserDefaults not working on Xcode beta with Watch OS2

For OS2 - you will need to use the WatchConnectivity frameworks and implement the WCSessionDelegate.

import WatchConnectivity
import WatchKit

@available(iOS 9.0, *)
var alertDelegate:HomeIC? = nil

public class WatchData: NSObject,WCSessionDelegate {
    var session = WCSession.defaultSession()
   //

    class var shared: WatchData {
        struct Static {
            static var onceToken: dispatch_once_t = 0
            static var instance: WatchData? = nil
        }
        dispatch_once(&Static.onceToken) {
            Static.instance = WatchData()
        }
        return Static.instance!
    }

    public func session(session: WCSession, didReceiveFile file: WCSessionFile){
        print(__FUNCTION__)
        print(session)

    }

    public func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
        print(__FUNCTION__)
        print(session)

        alertDelegate?.showMessage("didReceiveApplicationContext")
    }


    public func sessionReachabilityDidChange(session: WCSession){
        print(__FUNCTION__)
        print(session)
        print("reachability changed:\(session.reachable)")
        let text = session.reachable ? "reachable" : "unreachable"
        alertDelegate?.showMessage(text)
    }

    public func sessionWatchStateDidChange(session: WCSession) {
        print(__FUNCTION__)
        print(session)
        print("reachable:\(session.reachable)")
       // alertDelegate?.showMessage("sessionWatchStateDidChange")
        if !session.receivedApplicationContext.keys.isEmpty {
            alertDelegate?.showMessage(session.receivedApplicationContext.description)
        }
    }

    public func session(session: WCSession, didReceiveMessageData messageData: NSData){

        if !session.receivedApplicationContext.keys.isEmpty {
            alertDelegate?.showMessage(session.receivedApplicationContext.description)
        }
    }


    public func session(session: WCSession, didReceiveMessage message: [String : AnyObject]){
        print(__FUNCTION__)
        if let data = message["data"] {
            alertDelegate?.showMessage(data as! String)
            return
        }
    }

    public func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
        print(__FUNCTION__)
        if let data = message["data"] {
            alertDelegate?.showMessage(data as! String)
            return
        }
        guard message["request"] as? String == "showAlert" else {return}

    }


    public func activate(){

        if WCSession.isSupported() {    //  it is supported
            session = WCSession.defaultSession()
            session.delegate = self
            session.activateSession()
            print("watch activating WCSession")
        } else {

            print("watch does not support WCSession")
        }

        if(!session.reachable){
            print("not reachable")
            return
        }else{
            print("watch is reachable")

        }
    }

}

Sample Usage

class HomeIC: WKInterfaceController {
    // MARK: Properties


    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Initialize the `WCSession`.
        WatchData.shared.activate()
        alertDelegate = self
    }

    internal func showMessage(msg:String){
       let defaultAction = WKAlertAction(title: msg, style: WKAlertActionStyle.Default) { () -> Void in }
       let actions = [defaultAction]
       self.presentAlertControllerWithTitle(  "Info",  message: "",  preferredStyle: WKAlertControllerStyle.Alert, actions: actions)
    }

}

enter image description here

in my iphone code / I can invoke sharing data here

 if #available(iOS 9.0, *) {
        WatchData.shared.sendInbox()
    } else {
        // Fallback on earlier versions
    }

And somewhere else I have another discrete singleton for watch data session.

@available(iOS 9.0, *)
public class WatchData: NSObject,WCSessionDelegate {
    var session = WCSession.defaultSession()
    var  payload:String = ""



    class var shared: WatchData {
        struct Static {
            static var onceToken: dispatch_once_t = 0
            static var instance: WatchData? = nil
        }
        dispatch_once(&Static.onceToken) {
            Static.instance = WatchData()
        }
        return Static.instance!
    }


    public func sessionReachabilityDidChange(session: WCSession){
        print(__FUNCTION__)
        print(session)
        print("reachability changed:\(session.reachable)")
        if (session.reachable){

        }

    }


    public func sessionWatchStateDidChange(session: WCSession) {
        print(__FUNCTION__)
        print(session)
        print("reachable:\(session.reachable)")
    }

    public func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
        print(__FUNCTION__)
        guard message["request"] as? String == "showAlert" else {return}
        guard let m = message["m"] as? String else { return }
        print("msg:",m)
    }


    public func sendInbox(){



        if (!session.reachable){
            if WCSession.isSupported() {    //  it is supported
                session = WCSession.defaultSession()
                session.delegate = self
                session.activateSession()
                print("iphone activating WCSession")
            } else {
                print("iphone does not support WCSession")
            }
            session.activateSession()
        }

        if(session.paired){
            if(session.watchAppInstalled){
                print("paired | watchAppInstalled")
            }
        }else{
           print("not paired | or no watchAppInstalled")
        }


        if(!session.reachable){
            print("not reachable")
            return
        }else{

            /*let transfer:WCSessionUserInfoTransfer =  (session.transferUserInfo(["data" : "Test2"]) as WCSessionUserInfoTransfer?)!
            if(transfer.transferring){
                print("-> iphone")
            }else{
                print("!-> iphone")
            }*/

            session.sendMessage(["data" :"test"],
                replyHandler: { reply in
                },
                errorHandler: { error in
                      print(error)
            })

        }

    }

}

Refer to sample watch os2 app

https://github.com/shu223/watchOS-2-Sampler/tree/20eeebeed66764d0814603e97d3aca5933236299

Solution 3

As @johndpope said, shared NSUserDefaults no longer work on WatchOS2.

I'm posting a simplified solution that's not as full featured as john's but will get the job done in most cases.

In your iPhone App, follow these steps:

Pick find the view controller that you want to push data to the Apple Watch from and add the framework at the top.

import WatchConnectivity

Now, establish a WatchConnectivity session with the watch and send some data.

if WCSession.isSupported() { //makes sure it's not an iPad or iPod
    let watchSession = WCSession.defaultSession()
    watchSession.delegate = self
    watchSession.activateSession()
    if watchSession.paired && watchSession.watchAppInstalled {
        do {
            try watchSession.updateApplicationContext(["foo": "bar"])
        } catch let error as NSError {
            print(error.description)
        }
    }
}

Please note, this will NOT work if you skip setting the delegate, so even if you never use it you must set it and add this extension:

extension MyViewController: WCSessionDelegate {

}

Now, in your watch app (this exact code works for Glances and other watch kit app types as well) you add the framework:

import WatchConnectivity

Then you set up the connectivity session:

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
    let watchSession = WCSession.defaultSession()
    watchSession.delegate = self
    watchSession.activateSession()
}

and you simply listen and handle the messages from the iOS app:

extension InterfaceController: WCSessionDelegate {

    func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
        print("\(applicationContext)")
        dispatch_async(dispatch_get_main_queue(), {
            //update UI here
        })
    }

}

That's all there is to it.

Items of note:

  1. You can send a new applicationContext as often as you like and it doesn't matter if the watch is nearby and connected or if the watch app is running. This delivers the data in the background in an intelligent way and that data is sitting there waiting when the watch app is launched.
  2. If your watch app is actually active and running, it should receive the message immediately in most cases.
  3. You can reverse this code to have the watch send messages to the iPhone app the same way.
  4. applicationContext that your watch app receives when it is viewed will ONLY be the last message you sent. If you sent 20 messages before the watch app is viewed, it will ignore the first 19 and handle the 20th one.
  5. For doing a direct/hard connection between the 2 apps or for background file transfers or queued messaging, check out the WWDC video.

Solution 4

Another way to communicate between the app and the watch is via wormhole:

https://github.com/mutualmobile/MMWormhole

Send:

[self.wormhole passMessageObject:@{@"titleString" : title} 
                  identifier:@"messageIdentifier"];

id messageObject = [self.wormhole messageWithIdentifier:@"messageIdentifier"];

Recieve:

[self.wormhole listenForMessageWithIdentifier:@"messageIdentifier" 
listener:^(id messageObject) {
// Do Something
}];
Share:
18,766
user3746428
Author by

user3746428

Updated on June 12, 2022

Comments

  • user3746428
    user3746428 almost 2 years

    I am trying to pass data from my app into my Apple Watch app. Basically, I am using the same method as I used for creating the today widget and so I am passing data through NSUserDefaults.

    The problem is, that when I run my app, the data does not update the labels in the Watch app as I would expect it to.

    Here is what I have...

    override init(context: AnyObject?) {
        // Initialize variables here.
        super.init(context: context)
    
        // Configure interface objects here.
        NSLog("%@ init", self)
    
        var defaults = NSUserDefaults(suiteName: "group.AffordIt")
        var totalBudgetCalculation = ""
        if (defaults!.stringForKey("totalBudgetWidget") != nil) {
                println("Worked")
            totalBudgetCalculation = defaults!.stringForKey("totalBudgetWidget")!
            initialBudgetLabel.setText("Initial: \(totalBudgetCalculation)")
        }
    
        var currentBudgetCalculation = ""
        if (defaults!.stringForKey("currentBudgetWidget") != nil) {
            currentBudgetCalculation = defaults!.stringForKey("currentBudgetWidget")!
            currentBudgetLabel.setText("Current: \(currentBudgetCalculation)")
        }
    }
    

    I tried putting this code in willActivate(), however that doesn't seem to make a difference.

    Anyone know where I am going wrong?

  • user3746428
    user3746428 over 9 years
    Great! Thanks a lot. The problem was that I hadn't set up the App Group for the Watch.
  • konrad.bajtyngier
    konrad.bajtyngier over 9 years
    Have you tested this method? Is it reliable?
  • Paul Wand
    Paul Wand over 9 years
    Mutual mobile has an example build that I ran, which did indeed transmit data from iPhone to the watch
  • Arturino
    Arturino about 9 years
    Would this also work for Watch<-->Watch communication?
  • John Rogers
    John Rogers about 9 years
    MMWormhole just uses the low-level Darwin notification system that's been around for quite some time, they've just built a wrapper around it. In fact, I've seen this method recommended by Apple engineers. I'd say it's reliable enough.
  • Susim Samanta
    Susim Samanta about 9 years
    @Brindy : I followed same steps but it's not working for me. I am getting nil data . Any debugging help..
  • Kudit
    Kudit about 9 years
    I did the same and I'm also getting nil data. Is this a problem with the simulator?
  • johndpope
    johndpope over 8 years
    for os 2 - use the apple WatchConnectivity frameworks.
  • John Goodstadt
    John Goodstadt over 8 years
    There is sample code using applicationContext() on github see: github.com/JohnGoodstadt/applicationContext
  • Alessandro
    Alessandro over 8 years
    @PaulWand does it only work on OS2? Seems like watchConnectivity is not available as framework, and I am targeting ios8.2+
  • Paul Wand
    Paul Wand over 8 years
    If you are targeting ios 8.2 you should be able to use the provided example. Use the 1.2.0 release so you don't need watchConnectivity. Append this to your podfile: pod 'MMWormhole', '~> 1.2.0' The 2.0 release of MMWormhole has been updated to support the WatchConnectivity framework. The documentation is available here: MMWormholeSession
  • Charlie
    Charlie over 8 years
    MMWormhole is very useful, It works for watch OS 2 apis also. But I am facing issues while transferring custom class objects from iPhone to watch. But It works best for transferring simple objects like strings and dictionaries.
  • Carlos.V
    Carlos.V over 7 years
    @JohnGoodstadt is it normal when send a message from phone to watch if is not reachable it does not deliver the message and will not retry later, but from watch to phone if is not reachable it wait until is reachable and the message finally is delivered?
  • technerd
    technerd about 7 years
    @Charlie : I am also facing same issue with custom object. Did you find any solution for that ?
  • Charlie
    Charlie about 7 years
    @technerd: You can try serializing the class objects the way ObjectMapper does. In fact we can use the same ObjectMapper.
  • Ibdakine
    Ibdakine about 5 years
    Thank you! This was fantastic.
  • Digvijaysinh Gida
    Digvijaysinh Gida over 3 years
    Perfect answer, it's saved my time