前提・実現したいこと
xcode8.2.1
swift3
私は現在iPhoneを使ってヘルスケアに格納されている心拍数の値を取得するアプリを考えております。
そのアルゴリズムは
1.アップルウォッチで心拍数の計測ヘルスケアに格納
↓
2.ヘルスキットを用いて、iPnoneでヘルスケアにアクセスし心拍数の値を取得
というものになっております。
- 2. という方法で実装することで利用者はデベロッパー登録をしないで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を用いて新しい心拍数の値を取得する
というものがあります。
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
check解決した方法
0
試行錯誤してこのようにすれば上手くいきました。
どうしてこうなったのかはよくわかりません。申し訳ありません。
"並び替え条件"の後の
"心拍数が更新されるたびに呼び出されるクエリの作成"の中に
"クエリ作成"の部分を挿入することで上手くいきました。
// 並び替え条件
let sortDescriptors: [NSSortDescriptor]? = nil
// 心拍数が更新されるたびに呼び出されるクエリの作成
let observeQuery = HKObserverQuery(sampleType: type2, predicate: nil, updateHandler: { (
query, completionHandler, error) in
// 実行後
if error == 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
}
}
}
DispatchQueue.global().async {
healthStore.execute(sampleQuery)
}
}
completionHandler()
})
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.33%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
質問への追記・修正、ベストアンサー選択の依頼
2016/12/22 11:21
こちらの質問が他のユーザから「やってほしいことだけを記載した丸投げの質問」という指摘を受けました
「質問を編集する」ボタンから編集を行い、調査したこと・試したことを記入していただくと、回答が得られやすくなります。