###前提・実現したいこと
xcode8.2.1
swift3
私は現在iPhoneを使ってヘルスケアに格納されている心拍数の値を取得するアプリを考えております。
そのアルゴリズムは
1.アップルウォッチで心拍数の計測ヘルスケアに格納
↓
2.ヘルスキットを用いて、iPnoneでヘルスケアにアクセスし心拍数の値を取得
というものになっております。
-
- という方法で実装することで利用者はデベロッパー登録をしないでiPhoneに心拍数の値を取得することができます。
コードを完全に書き終わりいざ実機でビルドを行なってみると、デバックコンソール内でエラーが発生してしましました。
このエラーを解決したいです。二つあるのですが、どちらか片方でも本当に助かります。
長くなっておりますが何卒よろしくお願いいたします。
[追記]
Start observe の方のエラーの件で
queryの初期化が必要だということがわかりました。
queryの初期化の設定をご存知でしたらご教授お願いいたいます。
###発生している問題・エラーメッセージ
デバックコンソール内 ===='Start observe'ボタンを押した時==== socket connected! 2016-12-22 09:58:53.865464 healthKitTest1[1028:252927] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'This query has already been executed and cannot be executed again.' *** First throw call stack: (0x1bdc0df7 0x1b023077 0x1bdc0d3d 0x29b7bb39 0x29b569a3 0xafd54 0xadd0c 0xbf0d57 0xbff011 0xbfeba7 0x1b61b937 0x1b61b490) libc++abi.dylib: terminating with uncaught exception of type NSException ===='Start'ボタンを押し、そのあと'データ読み込み'ボタンを押した時==== my heartrate is nil (さらに self.getHeartRateAverage = (result!.averageQuantity()?.description)! のところでブレイクポイントが自動に入る)
###該当のソースコード
import UIKit import HealthKit class ViewController: UIViewController { let readDataButton = UIButton() let setTimerButton = UIButton() var setTimeFlag = 0 var startTime = Date() var endTime = Date() let formatter = DateFormatter() var getHeartRateAverage = String() var getHeartRateMax = String() var getHeartRateMin = String() let observeButton = UIButton() var observeSwitch: Int = 0 var getLatestHeartRate = String() override func viewDidLoad() { super.viewDidLoad() // データ取得ボタン readDataButton.setTitle("データ取得", for: .normal) readDataButton.addTarget(self, action: #selector(ViewController.didTapReadData), for: .touchUpInside) readDataButton.sizeToFit() readDataButton.center = CGPoint(x: view.center.x, y: view.center.y + 50) view.addSubview(readDataButton) // タイマーセット setTimerButton.setTitle("Start", for: .normal) setTimerButton.addTarget(self, action: #selector(ViewController.setTime), for: .touchUpInside) setTimerButton.sizeToFit() setTimerButton.center = CGPoint(x: view.center.x, y: view.center.y - 50) view.addSubview(setTimerButton) // 時間の表示の型 formatter.dateFormat = "yyyy/MM/dd HH:mm:ss" // HKHKObserverQuery起動、停止ボタンの作成 observeButton.setTitle("Start observe", for: .normal) observeButton.addTarget(self, action: #selector(ViewController.didTapObserveData), for: .touchUpInside) observeButton.sizeToFit() observeButton.center = CGPoint(x: view.center.x, y: view.center.y - 100) view.addSubview(observeButton) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // 開始時間と終了時間の設定 func setTime(){ if setTimeFlag == 0 { setTimeFlag = 1 setTimerButton.setTitle("Stop", for: .normal) startTime = NSDate() as Date let startStr = formatter.string(from: startTime) print("startTime : \(startStr)") } else { setTimeFlag = 0 setTimerButton.setTitle("Start", for: .normal) endTime = NSDate() as Date let endStr = formatter.string(from: endTime) print("endTime : \(endStr)") } } // HealthStoreから心拍数データを取得します。 func didTapReadData(){ readHealthData() } private func readHealthData(){ // データの種類 let type2 = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)! // データ取得の準備 let healthStore = HKHealthStore() // 取得するデータの条件 let predicate: NSPredicate = HKQuery.predicateForSamples(withStart: startTime, end: endTime, options: HKQueryOptions.strictStartDate) // 取得するための条件の設定 let startOptions: HKStatisticsOptions = ([HKStatisticsOptions.discreteAverage, HKStatisticsOptions.discreteMax, HKStatisticsOptions.discreteMin]) // データ抽出クエリ let query: HKStatisticsQuery = HKStatisticsQuery(quantityType: type2, quantitySamplePredicate: predicate, options: startOptions, completionHandler: { (query, result, error) in DispatchQueue.global().async { print("my heartrate is \(result!.averageQuantity())") self.getHeartRateAverage = (result!.averageQuantity()?.description)! self.getHeartRateMax = (result!.maximumQuantity()?.description)! self.getHeartRateMin = (result!.minimumQuantity()?.description)! } }) print("\(getHeartRateAverage)") // HealthStoreへのアクセス権限確認 let authorizedStatus2 = healthStore.authorizationStatus(for: type2) // 権限があれば、実行。権限がなければ、権限確認画面へ。 if authorizedStatus2 == .sharingAuthorized{ healthStore.execute(query) }else{ healthStore.requestAuthorization(toShare: [type2], read: [type2]) { success, error in if error != nil { return } if success { // 引数に指定されたクエリーを実行します healthStore.execute(query) } } } func didTapObserveData() { // データの種類 let type2 = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)! // データ取得の準備 let healthStore = HKHealthStore() // HealthStoreへのアクセス権限確認 // 権限があれば、実行。権限がなければ、権限確認画面へ。 healthStore.requestAuthorization(toShare: nil, read: [type2], completion: { (Bool, Error) -> Void in }) // 最新の心拍数を取得するクエリの作成 // 検索条件 let predicate: NSPredicate? = nil // 取得件数 let limit = 0 // 並び替え条件 let sortDescriptors: [NSSortDescriptor]? = nil // クエリ作成 let sampleQuery = HKSampleQuery(sampleType: type2, predicate: predicate, limit: limit, sortDescriptors: sortDescriptors) { (query, results, error) in // 実行後 if error == nil { DispatchQueue.global().async { // 最新の心拍数を格納 self.getLatestHeartRate = results!.description } } } // 心拍数が更新されるたびに呼び出されるクエリの作成 let observeQuery = HKObserverQuery(sampleType: type2, predicate: nil, updateHandler: { (query, complitionHandler, error) in // 実行後 if error == nil { DispatchQueue.global().async { healthStore.execute(sampleQuery) healthStore.stop(sampleQuery) } } }) if observeSwitch == 0 { observeSwitch = 1 healthStore.execute(observeQuery) observeButton.setTitle("Stop observe", for: .normal) } else { observeSwitch = 0 healthStore.stop(observeQuery) observeButton.setTitle("Start observe", for: .normal) } } }
###補足情報(言語/FW/ツール等のバージョンなど)
アプローチの仕方が2通りあります。
1つ目は指定した時間内の心拍数の最大最小平均値を取り出す処理を実装したくて
まず'Start'ボタンを2回(2回目は名前が'Stop'に変わる)押して測定開始時間、測定終了時間を保存
次にデータ読み込みボタンを押してHKStatisticsQueryを用いて最大最小平均値を取得する
2つ目はリアルタイムで心拍数を取り出す処理をしたくて
最初'Start observe'ボタンを押して、HKObserverQueryを用いてHealthStoreの値の変更を検知し、
その時sampleQueryを用いて新しい心拍数の値を取得する
というものがあります。
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。