WatchConnectivity - transerUserInfo is sending, but not receiving data - ios

I've been making an app that uses WatchConnectivity to transfer a simple struct from the Apple Watch to iPhone, and have been running into some troubles. Sending is perfectly fine, and both devices are reachable and activated on the same session, but the iPhone never seems to receive the struct I send it.
Here's my current code. I decided to use transferUserInfo to allow background transfer of data.
The struct:
struct myDataList {
var xAcc: [Int]
var timestamps: [Int]
}
Watch (Sending):
func sendTestData(data:myDataList) {
print("sending file to iphone")
if WCSession.default.activationState == WCSessionActivationState.activated && WCSession.isSupported() && WCSession.default.isReachable {
WCSession.default.transferUserInfo(["Data" : data])
}
else {
print("Could not send")
}
}
iPhone (Receiving):
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
print("received something")
DispatchQueue.main.async {
if let data = userInfo["Data"] as? myDataList {
for (acc,time) in zip(data.xAcc,data.timestamps){
let dataLine: String = "\(acc),\(time)\n"
self.appendToFile(file: "data", data: dataLine)
}
}
}
}
On both devices I've started a session like so:
if WCSession.isSupported() {
WCSession.default.delegate = self
WCSession.default.activate()
}
I've tested the other functions to write to file/etc and they are working individually. I'd appreciate feedback and advice on how to resolve this. Cheers!

Related

How to update state from Phone view on WatchOS view?

I'm using a companion app to authorize a user with a 3rd party service. Once authorized, I update a UserDefaults variable to true. On the companion app side, the view updates correctly and shows that the user has been authenticated. However, on the watch OS side the view does not update. Would I need to use the Watch Connectivity API and send a message to the watch to update the state? Or is there a simple way?
Phone App
struct AuthenticationView: View {
#State private var startingWebAuthenticationSession = false
#AppStorage("authorized") private var authorized = false
var body: some View {
Group {
if !authorized {
VStack {
Button("Connect", action: { self.startingWebAuthenticationSession = true })
.webAuthenticationSession(isPresented: $startingWebAuthenticationSession) {
WebAuthenticationSession(
url: URL(string: "https://service.com/oauth/authorize?scope=email%2Cread_stats&response_type=code&redirect_uri=watch%3A%2F%2Foauth-callback&client_id=\(clientId)")!,
callbackURLScheme: callbackURLScheme
) { callbackURL, error in
guard error == nil, let successURL = callbackURL else {
return
}
let oAuthCode = NSURLComponents(string: (successURL.absoluteString))?.queryItems?.filter({$0.name == "code"}).first
guard let authorizationCode = oAuthCode?.value else { return }
let url = URL(string: "https://service.com/oauth/token")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
let params = "client_id=\(clientId)&client_secret=\(clientSecret)&grant_type=authorization_code&code=\(authorizationCode)&redirect_uri=\(callbackURLScheme)://oauth-callback";
request.httpBody = params.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Error took place \(error)")
return
}
if let data = data, let response = String(data: data, encoding: .utf8) {
let accessTokenResponse: AccessTokenResponse = try! JSONDecoder().decode(AccessTokenResponse.self, from: response.data(using: .utf8)!)
let defaults = UserDefaults.standard
authorized = true
startingWebAuthenticationSession = false
defaults.set(accessTokenResponse.access_token, forKey: DefaultsKeys.accessToken) //TODO: Store securely
ConnectivityService.shared.send(authorized: true)
}
}
task.resume()
}
.prefersEphemeralWebBrowserSession(false)
}
}
}
else {
VStack {
Text("Authenticated!")
}
}
}
}
}
WatchOS
import SwiftUI
struct ConnectView: View {
#ObservedObject var connectivityService: ConnectivityService
var body: some View {
if !$connectivityService.authorized.wrappedValue {
VStack {
Text("Open the app on your primary device to connect.")
}
}
else {
//Some other view
}
}
}
EDIT:
Trying with Watch Connectivity API but the issue I'm experiencing is that when I authenticate from the phone, it'll take some time for the ConnectView to update the authorized variable. I know Watch Connectivity API doesn't update right away but at minimum I'd need some way for the watch to pick up that a secret access token has been retrieved and it can transition to the next view; whether that's through a shared state variable, UserDefaults, or whatever other mechanism.
Here is the ConnectivityService class I'm using:
import Foundation
import Combine
import WatchConnectivity
final class ConnectivityService: NSObject, ObservableObject {
static let shared = ConnectivityService()
#Published var authorized: Bool = false
override private init() {
super.init()
#if !os(watchOS)
guard WCSession.isSupported() else {
return
}
#endif
WCSession.default.delegate = self
WCSession.default.activate()
}
public func send(authorized: Bool, errorHandler: ((Error) -> Void)? = nil) {
guard WCSession.default.activationState == .activated else {
return
}
#if os(watchOS)
guard WCSession.default.isCompanionAppInstalled else {
return
}
#else
guard WCSession.default.isWatchAppInstalled else {
return
}
#endif
let authorizationInfo: [String: Bool] = [
DefaultsKeys.authorized: authorized
]
WCSession.default.sendMessage(authorizationInfo, replyHandler: nil)
WCSession.default.transferUserInfo(authorizationInfo)
}
}
extension ConnectivityService: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { }
func session(
_ session: WCSession,
didReceiveUserInfo userInfo: [String: Any] = [:]
) {
let key = DefaultsKeys.authorized
guard let authorized = userInfo[key] as? Bool else {
return
}
self.authorized = authorized
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
self.authorized = true
}
#if os(iOS)
func sessionDidBecomeInactive(_ session: WCSession) {
}
func sessionDidDeactivate(_ session: WCSession) {
WCSession.default.activate()
}
#endif
}
I tried doing these two lines but they have varying results:
WCSession.default.sendMessage(authorizationInfo, replyHandler: nil)
WCSession.default.transferUserInfo(authorizationInfo)
In the first line, XCode will say that no watch app could be found, even though I'm connected to both physical devices through XCode; launch phone first then watch. I believe the first one is immediate and the second is more of when the queue feels like it. Sometimes if I hard close the watch app, it'll pick up the state change in the authorized variable, sometimes it won't. Very frustrating inter-device communication.
UserDefaults doesn't pick up the access token value on the watch side. Maybe I have to use App Groups?
I do see this error on the Watch side:
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
So I thought to try and encapsulate the self.authorized = authorized call into something like:
DispatchQueue.main.async {
self.authorized = authorized
}
But it didn't do anything as far as solving the immediate state change issue.

How can i check from watch app that if user logged-in or not into the phone application

I am trying to add watch extension to my existing application. Login is compulsory for my application. How can i check from watch app if user logged-in or not... and as user gets loged-in, I want to pass that login-data from the application to the watchapplication. I don't know how to pass the login data to watch application.
Prior to WatchOS 2, you could share data between iOS companion app and watchOS app by using shared group container or iCloud to exchange data.
From WatchOS 2, since WatchKit extension now runs on Apple Watch itself, the extension must exchange data with the iOS app wirelessly. You will have to use WCSession class which is part of the WatchConnectivity framework. The framework is used to implement two-way communication between an iOS app and its paired watchOS app.
In iPhone companion app. Implement the following:
import WatchConnectivity
class LoginViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if WCSession.isSupported() {
WCSession.default().activate()
}
}
#IBAction func loginAction(_ sender: Any) {
// In this case ApiHandler is just a class which performs REST call to authenticate login credentials.
ApiHandler.login(username: txtUsername.text!, password: txtPassword.text!, { (loginSuccess) in
if loginSuccess {
let dict = ["statusLogin": "success"]
WCSession.default().sendMessage(dict, replyHandler: { (replyMessage) in
print(replyMessage)
}) { (error) in
print(error.localizedDescription)
}
}
})
}
}
In watchOS app.
import WatchConnectivity
class BaseInterfaceController: WKInterfaceController {
override func willActivate() {
super.willActivate()
if WCSession.isSupported() {
WCSession.default().delegate = self
WCSession.default().activate()
}
}
}
extension BaseInterfaceController: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Swift.Void) {
if let loginStatus = message["statusLogin"] as? String {
if loginStatus == "success" {
// login has been success in iOS app
// perform further tasks, like saving in Userdefaults etc.
}
}
}
}
You can do this using Watch Connectivity. Specifically sending a boolean value with transferUserInfo(). This will allow you to send the data from the iPhone application when the user either logs in or out, and it will be delivered to the watch extension in the background.
Here is an example:
func updateUserLoggedIn(_ loggedInValue : Bool) {
if WCSession.isSupported() {
WCSession.default.transferUserInfo(["User Logged In" : loggedInValue])
}
}
Then you simply handle the transfer in your watch extension:
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
if let userInfoFromPhone = userInfo["User Logged In"] {
// Handle Result Accordingly
}
}

Watch Connectivity works the first time but not after that

I've tested on the Simulator and Device, somehow Watch Connectivity stops working after it's used once.
I'm passing data from Watch -> iPhone, and it only works once and then stops after that.
Any ideas?
iPhone ViewController:
var session: WCSession?
override func viewDidLoad() {
super.viewDidLoad()
if WCSession.isSupported() {
session = WCSession.default()
session?.delegate = self
session?.activate()
}
}
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
// Received Application Context from Watch
let type = applicationContext["watchType"]
print("Type iPhone: \(type)")
DispatchQueue.main.async {
self.type.text = "Type: \(type)"
}
}
Watch InterfaceController:
let session = WCSession.default()
override func awake(withContext context: Any?) {
super.awake(withContext: context)
if WCSession.isSupported() {
session.delegate = self
session.activate()
}
}
#IBAction func startPressed() {
saveEverything()
}
func saveEverything() {
let watchContextType = ["watchType" : "Boxing/Running"]
do {
print("Type Watch: \(watchContextType)")
try session.updateApplicationContext(watchContextType)
} catch {
print("Didn't work")
}
}
When you use the updateApplicationContext(), you need to change the parameters for each call, otherwise the msg will not be delivered. I belive this is to conserve battery.
Anyway, try sending your message using sendMessage()or sendMessageData(), then the messages get delivered each time, even when they have the same contents. And, they are higher priority than updateApplicationContext so it's win-win :)
Here is the documentation: https://developer.apple.com/library/content/documentation/General/Conceptual/WatchKitProgrammingGuide/SharingData.html#//apple_ref/doc/uid/TP40014969-CH29-SW1
If you want to Drain Battery of your apple watch app Quickly then above technique of #Jens is well and good.
Instead of shifting from updateApplicationContext() to sendMessage() better make small change in your project to get Desired result.
I will explain the question scenario and then solution along with Demo app :-
#IBAction func sliderChange(_ value: Float)
{
let str:String?
switch value {
case 0...7 :
str = "Running"
case 8...14 :
str = "Sleeping"
default:
str = "normal"
}
updateContext(value: str!)
}
func updateContext(value:String) {
let dictionary = [ "watchType" : value ]
do {
print("update application context is called do statemet")
try session?.updateApplicationContext(dictionary)
}
catch{
print("this is the catch statement")
}
}
With update in slider Values in Watch ,iPhone values get updated .As you can see there is repetition of values for iPhone i.e
When sliderValue are from 0 to 7 values remain "Running" && "Sleeping" for
8 to 14 .
App work fine if I varies values of slide and desired result is reflected in iPhone in normal scenario.
Scenrio where it fails :-
i)Change the slider values from 0 to 3 then "Running" is reflected in iPhone. Thant's fine .
ii)Now close the iPhone application then change the slider values from 3 to 5 no we can see real problem when iPhone is opened back.
iii)Values is not triggered to iPhone .
Due to internal caching mechanism of updateApplicationContext() restrict
to trigger of duplicate values to iPhone .
Store the last updated state in didReceiveApplicationContext() and display state according to stored value .
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
checkStatus()
updateSavedState()
}
func checkStatus() {
print("check status")
if WCSession.isSupported() {
session = WCSession.default()
session?.delegate = self
session?.activate()
}
}
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
let type = applicationContext["watchType"]!
DispatchQueue.main.async {
self.updateLabel.text = " Type: \(type)"
UserDefaults.standard.set(type, forKey: "savedState") //setObject
}
}
func updateSavedState() {
self.updateLabel.text = UserDefaults.standard.string(forKey: "savedState")
}
Now everything is work perfect.
Demo App.

WCSession.sendMessage works 50/50

Lately, I am working on a project is related to Watch/iPhone communication again. But my code works sometimes and doesn’t work sometimes which is kind of weird to me because I think the code should either work or not. It cannot be 50/50. Therefore, I have no idea what goes wrong.
setup WCSession on iPhone:
class WatchCommunicationController: NSObject, WCSessionDelegate {
var session : WCSession?
override init(){
// super class init
super.init()
// if WCSession is supported
if WCSession.isSupported() { // it is supported
// get default session
session = WCSession.defaultSession()
// set delegate
session!.delegate = self
// activate session
session!.activateSession()
} else {
print("iPhone does not support WCSession")
}
}
... ...
}
similar WCSession setup on Watch:
class PhoneCommunicationController: NSObject, WCSessionDelegate {
var session : WCSession?
override init(){
// super class init
super.init()
// if WCSession is supported
if WCSession.isSupported() { // it is supported
// get default session
session = WCSession.defaultSession()
// set delegate
session!.delegate = self
// activate session
session!.activateSession()
} else {
print("Watch does not support WCSession")
}
}
... ...
}
send out message on Watch:
func sendGesture(gesture : GKGesture){
// if WCSession is reachable
if session!.reachable { // it is reachable
// create the interactive message with gesture
let message : [String : AnyObject]
message = [
"Type":"Gesture",
"Content":gesture.rawValue
]
// send message
session!.sendMessage(message, replyHandler: nil, errorHandler: nil)
print("Watch send gesture \(gesture)")
} else{ // it is not reachable
print("WCSession is not reachable")
}
}
related enum:
enum GKGesture: Int {
case Push = 0, Left, Right, Up, Down
}
receive message on iPhone:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
//retrieve info
let type = message["Type"] as! String
let content = message["Content"]
switch type {
case "Gesture":
handleGesture(GKGesture(rawValue: content as! Int)!)
default:
print("Received message \(message) is invalid with type of \(type)")
}
}
func handleGesture(gesture : GKGesture){
print("iPhone receives gesture \(gesture)")
var notificationName = ""
switch gesture {
case .Up:
notificationName = "GestureUp"
case .Down:
notificationName = "GestureDown"
case .Left:
notificationName = "GestureLeft"
case .Right:
notificationName = "GestureRight"
case .Push:
notificationName = "GesturePush"
}
NSNotificationCenter.defaultCenter().postNotificationName(notificationName, object: nil)
}
somehow I can’t debug my Watch app on Xcode, the debug session just won’t attach. I don’t know why. Therefore, I debug one-sided with just the iPhone.
sometimes I got "receives gesture” print out, and sometimes not. And the same for getting the notification.
I don't know if Int would be wrapped around to NSNumber while being transfer within WCSession. If it would be, then that must be why when I use Int as the base class of the enum it won't work and works when String is the base class.
Connectivity Known Issue Your app may crash when using NSNumber and
NSDate objects with the WCSession API.
Workaround: Convert an NSNumber or NSDate object to a string before
calling WCSession APIs. Do the opposite conversion on the receiving
side.
Watch OS 2 Beta 4 release note
My guess is your call to sendMessage is returning an error in the cases where it fails, but you haven't implemented the error handler!! For now while you are getting up and running you can get away with just printing the error, but if this is shipping code you really ought to handle the appropriate errors:
// send message
session.sendMessage(message, replyHandler: nil, errorHandler: { (error) -> Void in
print("Watch send gesture \(gesture) failed with error \(error)")
})
print("Watch send gesture \(gesture)")
Your flow is correct but the difficulty is to understand how to debug:
Debug Watch:
Run the iPhone target and when it is done hit the Stop button.
Open the iOS app inside the simulator (run it manually from the simulator and not from Xcode) and let it hang there.
Switch to the Watch target (yourAppName WatchKit App), put the relevant breakpoint and run it.
The iOS app will be put automatically in the background and then you will be able to use sendMessage method (at the Watch target) to send whatever you need and if you have a replayHandler in your iOS app you will even receive the relevant messages inside the sendMessage at your Watch target (i.e InterfaceController)
Small Swift example:
Sending a Dictionary from Watch to iOS app:
if WCSession.defaultSession().reachable == true {
let requestValues = ["Send" : "From iWatch to iPhone"]
let session = WCSession.defaultSession()
session.sendMessage(requestValues,
replyHandler: { (replayDic: [String : AnyObject]) -> Void in
print(replayDic["Send"])
}, errorHandler: { (error: NSError) -> Void in
print(error.description)
})
}
else
{
print("WCSession isn't reachable from iWatch to iPhone")
}
Receiving the message from the Watch and sending a replay from the iOS app:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
print(message.values)
var replyValues = Dictionary<String, AnyObject>()
replyValues["Send"] = "Received from iphone"
// Using the block to send back a message to the Watch
replyHandler(replyValues)
}
Debug iPhone:
The exact opposite of debug watch
Also, the answer by #sharpBaga has an important consideration.

Passing data to Apple Watch app

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?
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
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
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.
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
}];
Just use watch connectivity for communicate between these two platform you can read more about this in apple document
https://developer.apple.com/documentation/watchconnectivity

Resources