Transfer Data From Apple Watch To iPhone in Swift
Transferring data from Apple Watch to iPhone is very similar to vice versa.
For global variable you probably should use updateApplicationContext()
of WCSession
:
let session = WCSession.default()
if session.activationState == .activated {
session.updateApplicationContext(["my_global": g_myGlobal])
}
On the iPhone you should assign delegate to the default WCSession
and activate it. In the WCSessionDelegate
, implement the following method:
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
let receivedGlobal = applicationContext["my_global"] as? TypeOfTheGlobal
}
Alternatively you can use sendMessage(_:replyHandler:errorHandler:)
but this will require iPhone to be reachable.
In general I would recommend you to follow Zak Kautz advice and read about WatchConnectivity. This is the most common way to communicate between watch and phone.
How can I share information between my iOS and Watch apps using WatchConnectivity?
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 backgroundtransferFile
: send a file in the backgroundsendMessage
: 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)
})
}
}
swift how to share data between Watch and iPhone on background
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.
Apple Watch Not Passing Data to iPhone - Swift
I think you have messed up in the sendMessage(), I cannot work out the replyHandler syntax, and you miss the errorHandler: parameter.
Anyway, I've tried your code, and with a few changes it would work.
1). In InterfaceController, the sendPressed():
var count = 0
@IBAction func SendPressed() {
//Send Data to iOS
let msg = ["Count" : "\(count)"]
if session.isReachable {
session.sendMessage(msg, replyHandler: nil, errorHandler: { (error) -> Void in
print("Error handler: \(error)")
})
count += 1
}
}
I've added a count, since the message must vary for each call (to conserve battery), so you can now press the button several times in a row. And a check to verify that the host application is reachable.
2.) In the ViewController, remember to update the GUI on the main thread:
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
DispatchQueue.main.async {
self.lablel.text = "Message : \(message)"
}
}
Otherwise the label will not update when you receive the data.
Let me know if it helps you!
Related Topics
Process Array in Parallel Using Gcd
What's the Difference Between Using or Not Using the 'Where' Clause with Generics
Optional Field Type Doesn't Conform Protocol in Swift 3
Why Should Not Directly Extend Uiview or Uiviewcontroller
Know When an Iteration Over Array with Async Method Is Finished
Single-Element Parethesized Expressions/Tuples VS Common Use of Parentheses
Swift Parsing Attribute Name for Given Elementname
Swiftui: How to Present View When Clicking on a Button
Swift Combine: How to Create a Single Publisher from a List of Publishers
How Set Rootviewcontroller in Scene Delegate iOS 13
Binary Operator Cannot Be Applied to Operands of Type Int and Int? Swift 3
Best Practice for Swift Methods That Can Return or Error
How to Test Whether Generic Variable Is of Type Anyobject
Swift: Optional Text in Optional Value
Expandable Sections Uitableview Indexpath Swift
Segue from a Slpagingviewswift Vc and Dismiss the Destination Vc to Return