How can I share information between my iOS and Watch apps using WatchConnectivity?
Asked Answered
S

1

10

[Disclaimer: this question is intended to be a wiki question to answer the frequent questions about sending data between the iOS and watch apps under the watchkit and watch-os tags.]

I am developing an Apple Watch app and would like to send data between the iOS app and its WatchKit extension. I have looked at the WatchConnectivity framework, but don't really understand the difference between all of its methods.

Which function should I use if I want to be able to send data even when my apps are in the background?

Which function should I use to send UI updates to the Watch?

Which function should I use to send large data?

Snuggle answered 3/8, 2017 at 15:48 Comment(4)
I know this is a self-answer Q&A pair, but there are already plenty of questions on this topic: How to share data between iPhone and Apple Watch using groups?, WatchOS 2.0 Sharing Data between iOS App and WatchOS App, among others. You should consider adding your answer to one of those instead of creating a completely new question.Barytes
Well, both questions you linked are quite old and about Objective-C. If you check my activity in the watchkit, watch-os-3 and apple-watch tags, you can see that I have answered quite a few questions about this particular topic myself and created this Q&A because I've felt that there's no general question/answer about the topic yet and this is a problem that is quite frequently asked about in the tags.Snuggle
Then maybe you should edit your question to ask "How can I share information between my iOS and Watch with the WatchConnectivity framework?" to narrow the scope down. If you want to keep it general, then I would suggest you make your question and answer Community Wiki.Barytes
Done, thanks for the input.Snuggle
S
20

At the time of writing this answer (watchOS3 is the current stable release and watchOS4 is in beta stage), the only option for direct communication between an iOS app and its WatchKit extension is the WatchConnectivity framework. (I said direct, because this Q&A is not concerned with using cloud technologies such as CloudKit to upload files to the internet from one device and download them on the other one.)

First, let's discuss which function of WCSession should be used for what purpose. For the code examples, please scroll down.

A quick, one-liner about each function and when to use them before diving deep into details:

  • updateApplicationContext: synchronise states between the apps, send data to be displayed on the UI (only use it to send small pieces of data)
  • transferUserInfo: send a dictionary of data in the background
  • transferFile: send a file in the background
  • sendMessage: send an instant message between at least the watch app is running in foreground

Detailed description

updateApplicationContext(_:) should be used if you want to synchronise your apps (such as keep the UI updated or send state information, like user logged in, etc.). You can send a dictionary of data. Subsequent calls to this function replace the previously sent dictionary, so the counterpart app only receives the last item sent using updateApplicationContext. The system tries to call this function at an appropriate time for it to receive data by the time it is needed and meanwhile minimising power usage. For this reason, the function can be called when neither app is running in the foreground, but WCSession needs to be activated for the transfer to succeed. Frequent calls trying to transfer large amount of data using updateApplicationContext might fail, so for this usage call transferUserInfo instead.

transferUserInfo(:) and transferCurrentComplicationUserInfo(:) should be used if you want to send data in the background that needs to be received by the other application. Subsequent calls to this method are queued and all information sent from one app to the other is received. transferCurrentComplicationUserInfo might be used to send complication-related data to the WatchKit extension using a high-priority message and waking up the WatchKit app if needed. However, be aware that this function has a daily limit and once it's exceeded, data is transferred using the transferUserInfo function.

transferFile(_:metadata:) is similar in implementation and nature to transferUserInfo, but it accepts a fileURL instead of a dictionary as its input parameter and hence it should be used to send files local to the device to its counterpart. Subsequent calls are queued. Received files must be saved to a new location in the session(_:didReceive:) method, otherwise they are deleted.

sendMessage(:replyHandler:errorHandler:) and sendMessageData(:replyHandler:errorHandler:) send data immediately to a counterpart app. The counterpart app must be reachable before calling this method. The iOS app is always considered reachable, and calling this method from your Watch app wakes up the iOS app in the background as needed. The Watch app is considered reachable only while it is installed and running. The transfer must be initiated in the foreground. Subsequent calls to this method are queued.

For more information please see App programming guide for watchOS - Sharing Data.

Now some code examples:

Set up WatchConnectivity in the iOS app's AppDelegate:

import UIKit
import WatchConnectivity

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        if WCSession.isSupported() {
            let session = WCSession.default()
            session.delegate = self
            session.activate()
        }
        return true
    }
}

extension AppDelegate: WCSessionDelegate {

    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        print("Message received: ",message)
    }

    //below 3 functions are needed to be able to connect to several Watches
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {}

    func sessionDidDeactivate(_ session: WCSession) {}

    func sessionDidBecomeInactive(_ session: WCSession) {}
}

Make your WatchKit class conform to WCSessionDelegate:

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

Using the instant messaging function, sendMessage:

In the WatchKit app use this code when you want to send information immediately to the iOS app.

if WCSession.isSupported() {
    print("WCSession supported")
    let session = WCSession.default()
    session.delegate = self
    session.activate()
    if session.isReachable {
        session.sendMessage(["Instant":"Message"], replyHandler: nil, errorHandler: { error in
            print("Error sending message",error)
        })
    }
}
Snuggle answered 3/8, 2017 at 15:48 Comment(12)
If you have anything to add, feel free to comment or suggest an edit. If you would like to ask for more details on some parts of the answer or more examples, don't hesitate to tell me in comments either.Snuggle
Doesn't work, tried this along inside AppDelegate and you can't call any method on the iOS device, only send the data.Gorgonzola
@Gorgonzola please specify "doesn't work". What methods are you trying to call that you cannot call? Without seeing your code it's quite hard to help.Snuggle
Inside session(_ session: WCSession, didReceiveMessage message: [String : Any]) any method you try to call thats on the iOS device doesn't load or work. example: let database = Database(); let data = database.fetchObjectsAsDictionary(); replyhandler(["response": data]); - Doesn't work.Gorgonzola
@Gorgonzola that seems to be an unrelated issue, so I'd suggest opening a new question with a minimal reproducible example to solve your issueSnuggle
I'm trying to make sense of Apple's demo code of updating the application context on the phone and receiving in on the watch. However, this never works for me and didReceiveApplicationContext is never called. When I update the context on the watch, didReceiveApplicationContext is called on the phone with the correct context. I wonder if I should report this issue to Apple or if I'm just missing something..Aldos
@Aldos is session.isReachable true? If not, you won't be able to send data between the phone and the watch. The documentation of WCSession.isReachable, it clearly states what criteria needs to be met for isReachable to be true.Snuggle
@DávidPásztor thanks for your fast response. isReachable is true when I'm updating the application context. I've submitted a TSI to Apple - maybe there's something wrong to my setup or it's just not working in the simulator.Aldos
Apple support came back to me saying it was "not adequate" to test WatchConnectivity in a simulator environment.Aldos
@Aldos that's good to know, thanks for the updateSnuggle
Is it possible to use this for transferring an array of dictionaries [[String : Any]]?Coonskin
@Coonskin you can either call the sendMessage method multiple times to send each dictionary of the array individually or you can simply wrap your array of dictionaries in another dictionary and send that enclosing dictionary using sendMessageSnuggle

© 2022 - 2024 — McMap. All rights reserved.