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()
}
Related
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)
}
}
I'm getting this crash : link
in my app ,i did many search abou it but i didn't find how to solve it.
i also enabled Zombies object & Analyzer of xcode but without succes.
Here is my code :
import UIKit
class CameraViewController: UIViewController{
#IBOutlet weak var playerContainer: UIView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var loading: UIActivityIndicatorView!
var device : GTLUserendpointGeneralCamera!
var myfoxCam : CloseliCameraDevice!
var retryToPlayCount = 0
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self,selector: "playerStatusChanged:",name: CameraPlayerStatusChangedNotification,object: nil)
NSNotificationCenter.defaultCenter().addObserver(self,selector: "playerStoppedDispose:",name: CameraPlayerStoppedNotification,object: nil)
}
override func viewDidAppear(animated: Bool) {
if(device.brand == "myfox"){
showLive()
}
}
override func viewWillDisappear(animated: Bool) {
MyFoxManager.sharedInstance.destroyPlayer()
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func showLive(){
loading.startAnimating()
MyFoxManager.sharedInstance.showLive(myfoxCam , image: self.imageView)
}
func playerStatusChanged(notification: NSNotification){
let waiting = notification.userInfo!["NSLocalizedDescription"] as! Bool
dispatch_async(dispatch_get_main_queue()) { [weak self] in
if let strongSelf = self {
if(waiting){
strongSelf.loading.startAnimating()
}else{
strongSelf.retryToPlayCount = 0
strongSelf.loading.stopAnimating()
}
}
}
}
func playerStoppedDispose(notification: NSNotification){
let code = notification.userInfo!["NSLocalizedFailureReason"] as! Int
dispatch_async(dispatch_get_main_queue()) { [weak self] in
if let strongSelf = self {
if(code & strongSelf.retryToPlayCount < 4){
strongSelf.retryToPlayCount = strongSelf.retryToPlayCount + 1
strongSelf.performSelector("showLive", withObject: nil, afterDelay: 2.0)
}
}
}
}
}
And
import UIKit
import Foundation
import CoreBluetooth
class MyFoxManager: NSObject {
static let sharedInstance = MyFoxManager()
var closeSDK : CloseliSDK!
var login = false
private override init() {
closeSDK = CloseliSDK(productKey: MyFoxAppId, withPassword: MyFoxAppSecret, serverType: "us")
}
func getCamera(device: GTLUserendpointGeneralCamera) -> CloseliCameraDevice? {
if(!login){
do {
try closeSDK.loginWithToken(device.appUrl, withAccount: device.url)
login = true
}catch{
return nil
}
}
do {
let myfoxCameras = try closeSDK.getCameraListError()
let myfoxCamera = myfoxCameras.filter{$0.deviceUUID == device.idDevice}
return myfoxCamera[0] as? CloseliCameraDevice
}catch {
return nil
}
}
func showLive(myfoxCam: CloseliCameraDevice, image: UIImageView){
closeSDK.preparetoLivePreview(myfoxCam , withUI: image)
}
func destroyPlayer(){
closeSDK.destoryPlayer()
}
}
Notification definition from third-party library :
/**
Notification for live preview status changing.
NOTICE: The notification may not be sent by CloseliSDK, so please fill parameter with sender to nil when adding observer
#param userInfo in NSNotification is a NSDictionary, value for NSLocalizedDescriptionKey is a NSNumber, YES means live preview is buffering, NO means live preview begin.
BOOL bWaiting = [[[notification userInfo] valueForKey:NSLocalizedDescriptionKey] boolValue];
*/
extern NSString *const CameraPlayerStatusChangedNotification;
/**
Notification for player stopped.
NOTICE: The notification may not be sent by CloseliSDK, so please fill parameter with sender to nil when adding observer
#param userInfo in NSNotification is a NSDictionary, value for NSLocalizedFailureReasonErrorKey is a NSNumber, represent the reason why it stopped, normally 0.
long stopReason = [[[notification userInfo] valueForKey:NSLocalizedFailureReasonErrorKey] longValue];
stopReason: 0x3261-means there is another client playing in p2p mode.
*/
extern NSString *const CameraPlayerStoppedNotification;
Any idea ?
Thanks in advance
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
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
I made a custom class that handles audio recording/playback and put a Protocol in that class. I implemented the Protocol in a UIViewController class and called my setDelegate method for my AudioHelper class.
I am getting a compile error that has to do with my init(). Not exactly sure how to get rid of the error:
use of 'self' in method call 'setupAudioSession' before super.init initializes self
override init() {
setupAudioSession()
super.init()
}
How do I resolve this error? And why do I have to override init()?
My AudioHelper class
import Foundation
import AVFoundation
class AudioHelper: NSObject, AVAudioRecorderDelegate {
var audioSession: AVAudioSession?
var audioRecorder: AVAudioRecorder?
var delegate: AudioRecorderProtocol?
class var sharedInstance: AudioHelper {
struct Static {
static var instance: AudioHelper?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = AudioHelper()
}
return Static.instance!
}
override init() {
setupAudioSession()
super.init()
}
func setDelegate(delegate: AudioRecorderProtocol) {
self.delegate = delegate
}
func setupAudioSession() {
audioSession = AVAudioSession.sharedInstance()
audioSession?.setCategory(AVAudioSessionCategoryPlayAndRecord, error: nil)
audioSession?.setActive(true, error: nil)
}
func createAudioMessageDirectory() {
let fm = NSFileManager.defaultManager()
if !fm.fileExistsAtPath(GlobalVars.kAudioMessageDirectory) {
var error: NSError?
if !fm.createDirectoryAtPath(GlobalVars.kAudioMessageDirectory, withIntermediateDirectories: true, attributes: nil, error: &error) {
println("Unable to create audio message directory: \(error)")
}
}
}
// MARK: Recording
func beginRecordingAudio() {
createAudioMessageDirectory()
var filepath = GlobalVars.kAudioMessageDirectory.stringByAppendingPathComponent("audiofile.aac")
var url = NSURL(fileURLWithPath: filepath)
var recordSettings = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVSampleRateKey: 8000.0,
AVNumberOfChannelsKey: 1,
AVEncoderBitRateKey: 12800,
AVLinearPCMBitDepthKey: 16,
AVEncoderAudioQualityKey: AVAudioQuality.Max.rawValue
]
println("Recorded Audio Message Saved: \(url!)")
var error: NSError?
audioRecorder = AVAudioRecorder(URL: url, settings: recordSettings as [NSObject : AnyObject], error: &error)
if error == nil {
if audioRecorder != nil {
audioRecorder!.delegate = self
audioRecorder!.record()
}
}
else {
println(error!.localizedDescription)
}
}
func stopRecordingAudio() {
if audioRecorder != nil {
audioRecorder!.stop()
}
}
func handleRecordAudioButtonLongPressGestureForState(state: UIGestureRecognizerState) {
if state == UIGestureRecognizerState.Ended {
stopRecordingAudio()
delegate?.onRecordAudioStop()
}
else if state == UIGestureRecognizerState.Began {
beginRecordingAudio()
delegate?.onRecordAudioStop()
}
}
func audioRecorderDidFinishRecording(recorder: AVAudioRecorder!, successfully flag: Bool) {
println("Record Audio Success: \(flag)")
delegate?.onRecordAudioFinished()
}
func audioRecorderEncodeErrorDidOccur(recorder: AVAudioRecorder!, error: NSError!) {
println("Record Audio Encode Error: \(error.localizedDescription)")
}
// MARK: Playback
func playAudioMessageFromUrl(messageId: String) {
if let url = NSURL(string: GlobalVars.kUrlAudioMessage + messageId) {
if let data = NSData(contentsOfURL: url) {
var error: NSError? = nil
let audioPlayer = AVAudioPlayer(data: data, error: &error)
if error == nil {
if audioPlayer != nil {
audioPlayer.numberOfLoops = 0
audioPlayer.volume = 1.0
audioPlayer.prepareToPlay()
audioPlayer.play()
}
}
else {
println("Audio playback error: \(error?.localizedDescription)")
}
}
}
}
}
protocol AudioRecorderProtocol {
func onRecordAudioStart()
func onRecordAudioStop()
func onRecordAudioFinished()
}
My UIViewController that implements the protocol (cut out extraneous code)
class ChatViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, AudioRecorderProtocol {
let audioHelper = AudioHelper.sharedInstance
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
// addDemoMessages()
setupGestureRecognizer()
setupKeyboardObserver()
setupViews()
setupTableView()
audioHelper.setDelegate(self)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
getUsersFromDb()
getMessagesFromDb()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
setCurrentVC()
tableView.reloadData()
if partnerUserId != nil && !db.doesUserExist(partnerUserId!) {
HttpPostHelper.profileGet(userId: partnerUserId!)
}
requestMessagesFromServer()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
ViewHelper.scrollTableViewToBottom(tableView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func handleRecordAudioButtonHold(sender: UILongPressGestureRecognizer) {
audioHelper.handleRecordAudioButtonLongPressGestureForState(sender.state)
}
func onRecordAudioStart() {
dispatch_async(dispatch_get_main_queue(), {
ViewHelper.showToast(NSLocalizedString("RECORDING", comment: ""))
self.recordAudioButton.imageView!.image = UIImage(named: "RecordAudioClicked")
})
}
func onRecordAudioStop() {
dispatch_async(dispatch_get_main_queue(), {
self.recordAudioButton.imageView!.image = UIImage(named: "RecordAudio")
})
}
func onRecordAudioFinished() {
HttpPostHelper.messageAudio(partnerUserId: partnerUserId)
}
func playAudioFromUrl(sender: UIButton) {
let messageId = messages[sender.tag].id
audioHelper.playAudioMessageFromUrl(messageId)
}
}
Just place it under super.init().
The object needs to be initialized first by the super class and then you can do your custom initialization.
override init() {
super.init()
setupAudioSession()
}
If you are working with the MVVM pattern, and you really need to call to some function before the super.init, you can always move that function to the ViewModel and call it from there. Just inject the viewModel as an injected dependency.