Pass Strings from iPhone - ios

I'm trying to pass the color for each Play found in my CloudKit database.
iPhone --> Watch
iOS TableViewController:
func getCloudKit() {
///...
let publicData = container.publicCloudDatabase
publicData.performQuery(query, inZoneWithID: nil) { results, error in
if error == nil { // There is no error
for play in results! {
let newPlay = Play()
do {
try WatchSessionManager.sharedManager.updateApplicationContext(["color" : newPlay.teamColor])
NSLog("NewPColor: %#", newPlay.teamColor)
} catch {
print(error)
}
self.objects.append(newPlay)
}
} else {
print(error)
}
}
}
When I NSLog what is getting passed in the iOS side, it logs the 3 colors perfectly.
2015-09-25 21:10:28.706 Play[16444] NewPColor: FDB927
2015-09-25 21:10:28.707 Play[16444] NewPColor: 000000
2015-09-25 21:10:28.708 Play[16444] NewPColor: 000000
But when I go to the Watch side, nothing shows up in the colorLabel. (I'm not sure if setText is even getting called there because "From iPhone" doesn't even show up.)
WatchKit InterfaceController:
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
let colorWatch = applicationContext["color"] as? String
dispatch_async(dispatch_get_main_queue()) {
if let colorWatch = colorWatch {
self.colorLabel.setText("From iPhone: \(colorWatch)")
}
}
}
Any ideas? Can post any extra code as needed, just let me know. Thanks!
EDIT: Per question here is my WCSession code
iOS WatchSessionManager
class WatchSessionManager: NSObject, WCSessionDelegate {
static let sharedManager = WatchSessionManager()
private override init() {
super.init()
}
private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
private var validSession: WCSession? {
if let session = session where session.paired && session.watchAppInstalled {
return session
}
return nil
}
func startSession() {
session?.delegate = self
session?.activateSession()
}
}
extension WatchSessionManager {
// Sender
func updateApplicationContext(applicationContext: [String : AnyObject]) throws {
if let session = validSession {
do {
try session.updateApplicationContext(applicationContext)
} catch let error {
throw error
}
}
}
// Receiver
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
// handle receiving application context
dispatch_async(dispatch_get_main_queue()) {
// make sure to put on the main queue to update UI!
}
}
}

N.B. You pass 'newPlay' in the appContext dictionary, yet print 'newPlayoff' in your log.
I presume elsewhere you have set the WCSession delegate and called activateSession

Related

iPhone not receiving applicationContext from AppleWatch

I have created a program to test sending data back and forth from iPhone and AppleWatch, and visa versa. I've set it up so there is a button on the AppleWatch and a button on the iPhone. When the iPhone one is pressed, it will send data and rename the button on the AppleWatch to whatever that data String was.
I then implemented the same code for AppleWatch to iPhone but for some reason iPhone doesn't seem to receive the data. Here's the code for iPhone:
// ViewController.swift
import UIKit
import Foundation
import WatchConnectivity
class WatchManager: UIViewController, WCSessionDelegate {
var watchSession: WCSession? {
didSet {
if let session = watchSession {
session.delegate = self
session.activate()
}
}
}
override func viewDidLoad(){
super.viewDidLoad()
watchSession = WCSession.default
}
private func sendDict(_ dict: [String: Any]) {
do {
try self.watchSession?.updateApplicationContext(dict)
} catch {
print("Error sending dictionary \(dict) to Apple Watch!")
}
}
#IBOutlet weak var transferButton: UIButton!
#IBAction func dataTransfer(_ sender: Any) {
sendDict(["DataKey": UUID().uuidString])
print("sent")
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("Session activation did complete")
}
public func sessionDidBecomeInactive(_ session: WCSession) {
print("session did become inactive")
}
public func sessionDidDeactivate(_ session: WCSession) {
print("session did deactivate")
}
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
print("phone received app context: ", applicationContext)
if let temperature = applicationContext["DataKey"] as? String {
self.transferButton.setTitle(temperature, for: .normal)
}
}
}
and AppleWatch:
// InterfaceController.swift
import WatchKit
import Foundation
import WatchConnectivity
class InterfaceController: WKInterfaceController {
var watchSession: WCSession? {
didSet {
if let session = watchSession {
session.delegate = self
session.activate()
}
}
}
#IBOutlet weak var temperatureLabel: WKInterfaceButton!
private func sendDict(_ dict: [String: Any]) {
do {
try self.watchSession?.updateApplicationContext(dict)
} catch {
print("Error sending dictionary \(dict) to iPhone!")
}
}
#IBAction func button() {
let urg = ["DataKey":UUID().uuidString]
sendDict(urg)
print("watch sent app context \(urg)")
}
}
extension InterfaceController: WCSessionDelegate {
#if os(iOS)
public func sessionDidBecomeInactive(_ session: WCSession) { }
public func sessionDidDeactivate(_ session: WCSession) {
session.activate()
}
#endif
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("Session activation did complete")
}
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
print("watch received app context: ", applicationContext)
if let temperature = applicationContext["DataKey"] as? String {
self.temperatureLabel.setTitle(temperature)
}
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
watchSession = WCSession.default
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
I've tried changing the name of the key (didn't work), I made it so the data value is always changing (UUID().uuidString) and kept that. Other things I tried consisted of creating a label and trying to rename that instead of the button which failed, and lastly instead of renaming the button just sending some confirmation that it received data back to the apple watch, which failed.
Any help would be much appreciated, I hope it's not a silly error.
I think updateApplicationContextis not the right method for your needs.
From the docs:
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.
If you want to send data back and forth while both apps are in foreground sendMessage should work.
In the end you may need to implement a combination of both methods. I suggest reading the following doc: https://developer.apple.com/documentation/watchconnectivity/wcsession
Edit:
Just to make the point with "In the end you may need to implement a combination of both methods" even clearer, I have added some sample code from one of my apps.
The method _sendData tries to send the current data via sendMessageData, if the watch is reachable. If not it updates the application context to have the data available as some as the watch app starts.
- (void)_sendCurrentData
{
if ([WCSession defaultSession].isPaired && [WCSession defaultSession].isWatchAppInstalled)
{
if ([WCSession defaultSession].isReachable)
{
// Send data to watch
[[WCSession defaultSession] sendMessageData:self.currentData
replyHandler:nil
errorHandler:^(NSError * _Nonnull error) {
[self _updateApplicationContext];
}];
}
else
{
[self _updateApplicationContext];
}
}
}
- (void)_updateApplicationContext
{
if ([WCSession defaultSession].isPaired && [WCSession defaultSession].isWatchAppInstalled)
{
NSError* error = nil;
[[WCSession defaultSession] updateApplicationContext:#{#"data": self.currentData}
error:&error];
if (error != nil)
{
NSLog(#"error while updating application context: %#", error.localizedDescription);
}
}
}
use self.watchSession?.transferUserInfo(dict) instead of self.watchSession?.updateApplicationContext(dict)
you'll got a call back in:
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
}

how to debug watch connectivity

I am attempting to start an HKWorkoutSession on the Apple Watch and send the heart rate data near-instantaneously to the iPhone for near real-time display. To accomplish this, I'm using WatchConnectivity's sendMessage method. I do not need replies from the phone/watch, so I'm using the version of sendMessage that doesn't use a reply handler.
The heart rate data isn't making it to the phone, and I'm struggling to understand why. I'm not getting any sorts of error messages that are helpful. How do I find out what is going wrong? I have attached both the phone app and the watch app to the debugger, but since the message never gets to the phone, I'm having a ton of trouble figuring out what's going on because obviously my breakpoints in the phone code aren't being reached. Earlier, I was using the replyHandler version of sendMessage, which has a closure for error handling. Using that, I was able to see that my errors were (sometimes) due a timeout in receiving a return message. The didReceiveMessage delegate on the phone was never reached, though, so I'm not surprised that it never sent its return message. I assume there's something wrong with the connection between the phone app and watch app, but I've checked the isReachable boolean, as well as isPaired and isWatchAppInstalled.
So I guess I'm wondering if you have any tips about debugging techniques for Watch Connectivity. And, if you want to take a look at my code too, here it is. Let me know if you see anything glaringly wrong about it. (Sorry it's a tad sloppy--just trying to get things to work right now.)
ViewController:
class ViewController: UIViewController, WCSessionDelegate {
#IBOutlet weak var bpmLabel: UILabel!
let healthStore = HKHealthStore()
var bpmArray = [Double]()
var wcSession: WCSession?
// WC Session Delegate methods
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("WC Session activation failed with error: \(error.localizedDescription)")
return
}
}
func sessionDidBecomeInactive(_ session: WCSession) {
print("WC has become inactive")
}
func sessionDidDeactivate(_ session: WCSession) {
print("WC was deactivated")
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
guard let bpm = message["bpm"] as? Double else { return }
DispatchQueue.main.async {
self.bpmArray.append(bpm)
self.bpmLabel.text = String(bpm)
}
override func viewDidLoad() {
super.viewDidLoad()
if WCSession.isSupported() {
wcSession = WCSession.default
wcSession?.delegate = self
wcSession?.activate()
}
if !(wcSession?.isPaired)! || !(wcSession?.isWatchAppInstalled)! {
print("PAIRING PROBLEM")
}
}
InterfaceController:
var wcSession: WCSession?
#IBOutlet var bpm: WKInterfaceLabel!
let healthStore = HKHealthStore()
var workoutSession: HKWorkoutSession?
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("WC Session activation failed with error: \(error.localizedDescription)")
return
}
}
func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) {
print("here")
}
func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
print("Workout session failure")
}
override func awake(withContext context: Any?) {
super.awake(withContext: context)
workoutSession?.delegate = self
guard let wcSess = self.wcSession else { return }
wcSess.activate()
}
// modified from https://github.com/coolioxlr/watchOS-2-heartrate/blob/master/VimoHeartRate%20WatchKit%20App%20Extension/InterfaceController.swift
func createHeartRateStreamingQuery() -> HKQuery? {
if !HKHealthStore.isHealthDataAvailable() {
print("health data not available")
}
guard let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else { return nil }
let heartRateQuery = HKAnchoredObjectQuery(type: quantityType, predicate: nil, anchor: nil, limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
self.updateHeartRate(samples: sampleObjects)
}
heartRateQuery.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
self.updateHeartRate(samples: samples)
}
return heartRateQuery
}
// modified from https://github.com/coolioxlr/watchOS-2-heartrate/blob/master/VimoHeartRate%20WatchKit%20App%20Extension/InterfaceController.swift
func updateHeartRate(samples: [HKSample]?) {
guard let heartRateSamples = samples as? [HKQuantitySample] else { return }
guard let sample = heartRateSamples.first else { return }
let value = sample.quantity.doubleValue(for: HKUnit(from: "count/min"))
DispatchQueue.main.async() {
self.bpm.setText(String(UInt16(value)))
}
let dataToSendToPhone = ["bpm":String(value)]
if (wcSession?.isReachable)! {
self.wcSession?.sendMessage(dataToSendToPhone, replyHandler: nil)
}
else {
print("WC Session not reachable")
}
}
override func willActivate() {
super.willActivate()
if WCSession.isSupported() {
wcSession = WCSession.default
wcSession?.delegate = self
wcSession?.activate()
}
}
override func didDeactivate() {
super.didDeactivate()
}
#IBAction func recordIsTapped() {
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .other
workoutConfiguration.locationType = .unknown
// Inspired by https://developer.apple.com/library/content/samplecode/SpeedySloth/Introduction/Intro.html
do {
try self.workoutSession = HKWorkoutSession(configuration: workoutConfiguration)
healthStore.start(self.workoutSession!)
if HKHealthStore.isHealthDataAvailable() {
if let query = createHeartRateStreamingQuery() {
self.healthStore.execute(query)
}
}
else {
print("Healthkit unavailable")
}
}
catch {
fatalError(error.localizedDescription)
}
}

Issues with sendMessage - WatchConnectivity

I've been using NatashaTheRobot singleton for the WCSession, but can't get the sendMessage to work properly.
My goal is to send a message from Watch app to the iOS app and transfer a dictionary from iOS app to watch app.
Here's my code in the ExtensionDelegate
import WatchKit
import WatchConnectivity
class ExtensionDelegate: NSObject, WKExtensionDelegate, WCSessionDelegate {
var session:WCSession!
var boolCheck = Int()
func applicationDidFinishLaunching() {
WatchSessionManager.sharedManager.startSession()
print("Here i am")
}
func applicationDidBecomeActive() {
print("I AWOKE")
}
func applicationWillResignActive() {
}
}
class WatchSessionManager: NSObject, WCSessionDelegate {
static let sharedManager = WatchSessionManager()
private override init() {
super.init()
}
private let session: WCSession = WCSession.defaultSession()
func startSession() {
session.delegate = self
session.activateSession()
if WCSession.isSupported(){
self.session.sendMessage(["b":"peek"], replyHandler: nil, errorHandler: nil)
print("works")
} else {
print("don't work")
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
let sweetN = message["b"]! as? String
dispatch_async(dispatch_get_main_queue(), {
if sweetN == "insertData1" {
NSNotificationCenter.defaultCenter().postNotificationName("sweetData1", object: nil)
})
}
func sendMessage(message: [String : AnyObject],
replyHandler: (([String : AnyObject]) -> Void)? = nil,
errorHandler: ((NSError) -> Void)? = nil)
{
session.sendMessage(message, replyHandler: replyHandler, errorHandler: errorHandler)
print("this is message \(replyHandler)")
var pretty = replyHandler
}
Here's my code in the WCSingleton in the iOS app (separate from the AppDelegate)
import WatchConnectivity
#available(iOS 9.0, *)
class WatchSessionManager: NSObject, WCSessionDelegate {
static let sharedManager = WatchSessionManager()
private override init() {
super.init()
}
private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
private var validSession: WCSession? {
if let session = session where session.paired && session.watchAppInstalled {
return session
}
return nil
}
func startSession() {
session?.delegate = self
session?.activateSession()
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
//receieve messages from watch
print(message["b"]! as? String)
let sweetN = message["b"]! as? String
dispatch_async(dispatch_get_main_queue(), {
if sweetN == "peek"{
NSNotificationCenter.defaultCenter().postNotificationName("giveMeInfo", object: nil)
}
}
})
}
#available(iOS 9.0, *)
extension WatchSessionManager {
func sendMessage(message: [String : AnyObject],
replyHandler: (([String : AnyObject]) -> Void)? = nil,
errorHandler: ((NSError) -> Void)? = nil)
{
session!.sendMessage(message, replyHandler: replyHandler, errorHandler: errorHandler)
}
}
and here's the method i use in the ViewController (fired from NSNotificationCenter). However this part of code never gets executed (which is strange, because when i use applicationContext it works perfectly).
func giveMeInfo(){
let linesAdd1 = linesAdd as! AnyObject
WatchSessionManager.sharedManager.sendMessage(["a":linesAdd1])
}
Any insights of how to get all those parts working together are very welcome!
Your Code is a bit confusing to me and looks right but you will run into troubles with this approach for the following reasons:
if you receive the notification in the ViewController and the Watch changes into the inactive state, the sendMessage() method will not work to send data back:
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. The errorHandler block may also be called if the message parameter contains non property list data types.
if you want to get data back, then you should use a reply block.
But in your configuration these blocks will not be called because:
sendMessage(reply==nil) --> didReceiveMessage(... message: )
sendMessage(reply!=nil) --> didReceiveMessage(... message: replyHandler:)
on the other hand if you use the contextMethod:
Use this method to transfer a dictionary of data items to the counterpart app. 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. The counterpart’s session delivers the data to the session:didReceiveUpdate: method of its delegate. A counterpart can also retrieve the data from the receivedApplicationContext property of its session.
I hope this helps ;)

TableView setup gets called before didReceiveUserInfo

My table keeps ending up empty because didRecieveUserInfo isn't called until after doTable() gets called.
I'm getting all the correct data in didRecieveUserInfo, but my app doesn't know that because didRecieveUserInfo isn't getting called until after the table is already set up.
Any ideas? I feel like it must be something simple, here is my code:
ExtensionDelegate:
func applicationDidFinishLaunching() {
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let tCValue = userInfo["Tr"] as? String, let mValue = userInfo["Mp"] as? String {
receivedData.append(["Tr" : tCValue , "Mp" : mValue])
evn.append(Evnt(dataDictionary: ["Tr" : tCValue , "Mp" : mValue]))
} else {
// None
}
}
InterfaceController:
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
doTable()
}
func doTable() {
if WCSession.defaultSession().reachable == true {
// SO
let myDelegate = WKExtension.sharedExtension().delegate as! ExtensionDelegate
let evM = myDelegate.evn
self.rowTable.setNumberOfRows(evM.count, withRowType: "rows")
for (index, evt) in evM.enumerate() {
if let row = rowTable.rowControllerAtIndex(index) as? TableRowController {
row.mLabel.setText(evt.maT)
} else {
// None
}
}
}
else {
// None
}
}
Implement the WCSession Delegate in the Interfacecontroller and call the doTable() after you receive your data

WatchKit data not displaying

I'm passing data from iOS to WatchKit. I can't get the data to show that was received on the WatchKit side somehow though.
This works fine: iOS TableViewController
func getCloudKit() {
///...
let publicData = container.publicCloudDatabase
publicData.performQuery(query, inZoneWithID: nil) { results, error in
if error == nil { // There is no error
for play in results! {
let newPlay = Play()
newPlay.tColor = play["TColor"] as! String
do {
try WatchSessionManager.sharedManager.updateApplicationContext(["color" : newPlay.tColor])
NSLog("NewPColor: %#", newPlay.tColor)
} catch {
print(error)
}
self.objects.append(newPlay)
}
} else {
print(error)
}
}
}
This isn't calling any of the NSLogs or showing any of the data: WatchKit InterfaceController
import WatchConnectivity
class InterfaceController: WKInterfaceController, WCSessionDelegate {
#IBOutlet var colorLabel: WKInterfaceLabel!
private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
private func configureWCSession() {
session?.delegate = self;
session?.activateSession()
}
override init() {
super.init()
configureWCSession()
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
}
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
let colorWatch = applicationContext["color"] as? String
NSLog("Check if anything here: %#", colorWatch!)
dispatch_async(dispatch_get_main_queue()) {
if let colorWatch = colorWatch {
self.colorLabel.setText("From iPhone: \(colorWatch)")
NSLog("Heres all the strings %#", colorWatch)
} else {
NSLog("Nothing")
}
}
}
}
Any ideas? Thanks!
Try checking if session is nil with something like (my swift foo is weak):
private func configureWCSession() {
print("session: \(session?))
session?.delegate = self;
session?.activateSession()
}

Resources