how to debug watch connectivity - ios

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)
}
}

Related

WCSession sendMessageData not working in watchOS 3

I am trying to send image from iOS to watchOS and I am getting below error.
sendMessageData getting error: Error Domain=WCErrorDomain Code=7014
"Payload could not be delivered."
UserInfo={NSLocalizedDescription=Payload could not be delivered.
My code in ViewController Class
var session: WCSession?
override func viewDidLoad() {
super.viewDidLoad()
session?.delegate = self
if (WCSession.isSupported()) {
session = WCSession.default()
session?.delegate = self
session?.activate()
}
}
let image = UIImage(named: "img1")!
let data = UIImagePNGRepresentation(image)
session?.sendMessageData(data!, replyHandler: { (data) in
print(data)
}) { (error) in
print(error)}
}
In InterfaceController Class
override func willActivate() {
super.willActivate()
if (WCSession.isSupported()) {
session = WCSession.default()
session.delegate = self
session.activate()
}
}
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
guard let image = UIImage(data: messageData) else {
return
}
print(image)
}
Thanks,
I think you need to try below protocol as mentioned here.
func session(_ session: WCSession, didReceiveMessageData messageData: Data, replyHandler: #escaping (Data) -> Void) {
guard let image = UIImage(data: messageData) else {
return
}
print(image)
imageSet.setImage(image)
}

Send Name and Birthday to Watch using WatchConnectivity

I’m accessing a users Contacts to send the contact Name and Birthday from the iPhone to the AppleWatch. I have access to the Contacts and can display the data on the phone, but I’m having trouble sending the info with WatchConnectivity. I'm using the Ray Wenderlich book WatchOS by Tutorials as a guide but am still having trouble.
Here is what I have on the Phone side to set up Watch Connectivity in AppDelegate.swift.
// MARK: Watch Connectivity
extension AppDelegate: WCSessionDelegate {
func sessionDidBecomeInactive(_ session: WCSession) {
print("WC Session did become inactive.")
}
func sessionDidDeactivate(_ session: WCSession) {
print("WC Session did deactivate.")
WCSession.default().activate()
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("WC Session activation failed with error:" + "\(error.localizedDescription)")
return
}
print("Phone activated with state:" + "\(activationState.rawValue)")
}
func setUpWatchConnectivity() {
if WCSession.isSupported() {
let session = WCSession.default()
session.delegate = self
session.activate()
}
}
}
And this in ViewController.swift
// MARK Watch Connectivity
extension ViewController {
func sendNameAndBirthdayToWatch() {
if WCSession.isSupported() {
let session = WCSession.default()
if session.isWatchAppInstalled {
let nameAndBirthday = ["name": nameLabel.text, "birthday": birthdayLabel.text]
do {
try session.updateApplicationContext(nameAndBirthday)
} catch {
print("Error")
}
}
}
}
}
I'm calling sendNameAndBirthdayToWatch() with a UIButton on the phone. As a prototype the phone currently displays the name and birthday on two separate labels pulled from the Contacts in the Xcode simulator so I at least know I'm getting the data.
On the watch in the ExtensionDelegate.swift I have.
// MARK: Watch Connectivity
extension ExtensionDelegate: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("Activation failed with error: \(error.localizedDescription)")
return
}
print("Watch activated with activation state: \(activationState.rawValue) ")
}
func setupWatchConnectivity() {
if WCSession.isSupported() {
let session = WCSession.default()
session.delegate = self
session.activate()
}
}
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
if let name = applicationContext["name"] as? String, let birthday = applicationContext["birthday"] as? String {
InterfaceController.sharedInterfaceController.updateWatchLabel(name: name, birthday: birthday)
}
}
}
This is where I'm stuck. I'm not sure where I've gone wrong and/or how to move forward.
I'm calling updateWatchLabel() in the InterfaceController.swift on the watch, but I'm not seeing any results.
Thank you in advance for any help. I've been staring at this for a solid week and unfortunately the example project in the Wenderlich book is a bit too complicated for my understanding.
I'm using Xcode 8.2.1 and Swift 3.
In time I answered my own question.
Here is the code on the phone in ViewController.swift. The code remains the same as in my original post in AppDelegate.swift.
func sendMessageToWatch() {
if WCSession.isSupported() {
let session = WCSession.default()
if session.isWatchAppInstalled {
do {
let message = ["message": "A message as String"]
try session.updateApplicationContext(message)
} catch {
print("Error: \(error)")
}
} else if session.isWatchAppInstalled == false {
print("No watch app installed")
}
}
}
I am at the moment calling this function by pressing a UIButton on the phone. Then on the watch side in InterfaceController.swift for the receiving end. I basically moved the code from ExtensionDelegate.swift to InterfaceController.swift.
// MARK: Watch Connectivity
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("Activation failed with error: \(error.localizedDescription)")
return
}
print("Watch activated with activation state: \(activationState.rawValue) ")
}
func setupWatchConnectivity() {
if WCSession.isSupported() {
let session = WCSession.default()
session.delegate = self
session.activate()
}
}
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
let message = applicationContext["message"] as? String
DispatchQueue.main.async(execute: {
self.label.setText(message)
})
}

Apple Watch Not Passing Data to iPhone - Swift

I'm trying to pass a String from my Apple Watch to an iPhone but it seems like it's not connecting. Here's my code:
ViewController.swift :
import UIKit
import WatchConnectivity
class ViewController: UIViewController, WCSessionDelegate {
#IBOutlet weak var lablel: UILabel!
var string = "Hello World"
let session = WCSession.default()
override func viewDidLoad() {
super.viewDidLoad()
session.delegate = self
session.activate()
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
let msg = message["StringValueSentFromiWatch"] as! String
lablel.text = "Message : \(msg)"
print("iphone recieved message")
}
func session(_ session: WCSession,
activationDidCompleteWith activationState: WCSessionActivationState,
error: Error?) {
}
func sessionDidBecomeInactive(_ session: WCSession) {
}
func sessionDidDeactivate(_ session: WCSession) {
}
}
InterfaceController.swift :
import WatchKit
import Foundation
import WatchConnectivity
class InterfaceController: WKInterfaceController, WCSessionDelegate {
let session = WCSession.default()
override func willActivate() {
super.willActivate()
session.delegate = self
session.activate()
}
#IBAction func SendPressed() {
//Send Data to iOS
let msg = ["StringValueSentFromiWatch" : "Hello World"]
session.sendMessage(msg, replyHandler: { (replay) -> Void in
print("apple watch sent")
}) { (error) -> Void in
print("apple watch sent error")
}
}
func session(_ session: WCSession,
activationDidCompleteWith activationState: WCSessionActivationState,
error: Error?){
}
}
I'm trying to send "Hello World" to the iPhone but I get this printout in the console:
errorHandler: YES with WCErrorCodePayloadUnsupportedTypes
and 'apple watch sent error'.
I know it's not sending but I don't know why. Does anyone know why this doesn't work?
Note: I'm running this is the simulator but I'm fairly sure this is not the problem.
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!

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()
}

Pass Strings from iPhone

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

Resources