Passing data to Apple Watch app
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?
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!
- 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)
}
}
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:
- 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.
- If your watch app is actually active and running, it should receive the message immediately in most cases.
- You can reverse this code to have the watch send messages to the iPhone app the same way.
- 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.
- 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
}];
user3746428
Updated on June 12, 2022Comments
-
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?
-
johndpope over 8 yearsplease rework this title to say OS1. this technique is no longer relevant in os2. stackoverflow.com/questions/30851729/…
-
-
user3746428 over 9 yearsGreat! Thanks a lot. The problem was that I hadn't set up the App Group for the Watch.
-
konrad.bajtyngier over 9 yearsHave you tested this method? Is it reliable?
-
Paul Wand over 9 yearsMutual mobile has an example build that I ran, which did indeed transmit data from iPhone to the watch
-
Arturino about 9 yearsWould this also work for Watch<-->Watch communication?
-
John Rogers about 9 yearsMMWormhole 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 about 9 years@Brindy : I followed same steps but it's not working for me. I am getting nil data . Any debugging help..
-
Kudit about 9 yearsI did the same and I'm also getting nil data. Is this a problem with the simulator?
-
johndpope over 8 yearsfor os 2 - use the apple WatchConnectivity frameworks.
-
John Goodstadt over 8 yearsThere is sample code using applicationContext() on github see: github.com/JohnGoodstadt/applicationContext
-
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 over 8 yearsIf 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 over 8 yearsMMWormhole 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 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 about 7 years@Charlie : I am also facing same issue with custom object. Did you find any solution for that ?
-
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 about 5 yearsThank you! This was fantastic.
-
Digvijaysinh Gida over 3 yearsPerfect answer, it's saved my time