LoginSignup
14
5

進化したHealthKitを使って睡眠分析アプリを作ってみる

Last updated at Posted at 2022-12-12

はじめに

本記事はARISE analytics Advent Calendar 2022の13日目です。
昨日は@hktechさんのAWS App Runnerでアプリケーションを動かす
明日は@SaitomTechさんのServerless Framework を用いて Step Functions Distributed Map による Lambda の大規模並列処理を実現する

HealthKitとは

HealthKitとは、iPhoneやApple Watch、他のヘルスケアアプリから集めた健康データを、iPhoneのヘルスケアアプリに連携する開発者向けツールです。例えば、Apple Watchで計測された心拍数や、体重計と連動しているアプリから連携される体重や体脂肪率をHealthKitが統合管理することで、我々開発者はさまざまなヘルスデータを利用することができます。
HealthKit.png

HealthKitで扱えるデータ(iOS16)

HealthKitで扱えるデータの種類はiOSの進化と共に増えています。今回はiOS16から進化した睡眠について見ていきます。

  • 栄養データ(39種)
  • アクティブティ(15種)
  • マインドフルネス(1種)
  • 睡眠(1種)
  • 身体測定値(6種)
  • リプロダクティブヘルス(6種)
  • 心臓(8種)
  • 検査結果(12種)
  • バイタル(4種)
  • ワークアウト(72種)

睡眠データについて

取得可能なデータについて

HealthKitでは睡眠分析用のデータ項目として、「SleepAnalysis」が用意されています。
SleepAnalysisでは、ユーザーの睡眠ステータスが変更された場合に以下のデータが書き込まれます。
SleepAnalysisはiOS15まで「寝ているか」 or 「起きているか」の情報しか取れませんでしたが、iOS16で詳細な睡眠ステータスが取得可能になりました。
スクリーンショット 2022-12-11 17.29.53.png
参考:https://developer.apple.com/documentation/healthkit/hkcategoryvaluesleepanalysis

データの書き込みタイミング

Apple Watchがユーザーの心拍数等の状態を監視し、睡眠ステータスが変更されたと判定されたタイミングでデータが書き込まれます。
スクリーンショット 2022-12-11 17.30.42.png

取得データのイメージ

書き込まれたデータは日付を指定してリストで取得可能です。
スクリーンショット 2022-12-11 16.55.13.png

アプリ側で起床を検知することはできるのか?

睡眠分析機能を作ろうとしたときに、起床時に何らかの処理を行いたいケースがあると思います。
そこで、アプリ側でユーザーが起床したかどうかをリアルタイムで検知できるか調査しました。
結論から言うと、データの監視自体は可能でが、リアルタイムでの検知は難しいようです。
HealthKitのデータを監視するにはObserval機能を使います。
Observal機能はアプリの状態(フォアグラウンド、バックグラウンド、キル)を問わず検知可能ですが、iPhoneがロック状態では検知ができません。
observal_image3.png
寝る時はiphoneをロックする人が大半だと思うので、起床後、初めてロック解除したタイミングで「X時に起床していた」という情報が取得できます。
参考:https://developer.apple.com/documentation/healthkit/hkobserverquery/executing_observer_queries

サンプルコード

権限リクエスト

let healthStore = HKHealthStore()
    let readTypes = Set([
        HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!
    ])
     
    healthStore.requestAuthorization(toShare: [], read: readTypes, completion: { success, error in
        if success == false {
            print("データにアクセスできません")
            return
        }
    })

睡眠データをHealthKitから取得

func getSleepAnalysis()  {
        let query = HKSampleQuery(sampleType: HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!,
                                  predicate: HKQuery.predicateForSamples(withStart: Calendar.current.date(byAdding: .month, value: -1, to: Date())!, end: Date(), options: []),
                                  limit: HKObjectQueryNoLimit,
                                  sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: true)]){ (query, results, error) in
            
            guard error == nil else { print("error"); return }
            if let results = results as? [HKCategorySample] {
                let reversedResults = results.reversed()
                DispatchQueue.main.async {
                    reversedResults.map { data in
                        print(data.value.description)
                        // 睡眠データを使った処理を書く
                    }
                }
            }
        }
        healthStore.execute(query)
    }

睡眠データをObservalで監視

func setObserver() {
        let sampleType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!
        var observerQuery = HKObserverQuery(sampleType: sampleType, predicate: nil, updateHandler: { query, completionHandler, error in
            if error != nil { return }
            // 睡眠データの追加が検知された場合にここに来る
            
            // 直近一時間の睡眠データを取得
            let query = HKSampleQuery(sampleType: sampleType,
                                      predicate: HKQuery.predicateForSamples(withStart: Calendar.current.date(byAdding: .hour, value: -1, to: Date())!, end: Date(), options: []),
                                      limit: HKObjectQueryNoLimit,
                                      sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: true)]){ (query, results, error) in
                
                guard error == nil else {
                    completionHandler()
                    print("error");
                    return
                    
                }
            
                if let sample = results as? [HKCategorySample] {
                    // 追加された睡眠データを用いた処理を書く
                }
            }
        })
        
        healthStore.execute(observerQuery)
        
        // バックグランドでのヘルスケアデータの更新検知を有効にする
        healthStore.enableBackgroundDelivery(for: sampleType, frequency: .immediate) { success, error in
            if let error = error {
                print(error)
            }
            print(success)
        }
    }

おわりに

HealthKitを扱うタイミングがあったので記事にしました。
HealthKitはiOSのアップデートと共にできることが増えており、睡眠データについてもiOS15までは「起きているか」 or 「寝ているか」の情報しか取得でいませんでした。しかし、iOS16のアップデートで睡眠の質についても分析することができるようになり、既存のヘルスヘアアプリに簡単に睡眠分析機能を追加可能になったと思います。
新しいApple Watchにも体温を扱う機能が追加され、今後もHealthKitのアップデートには注目したいですね。

14
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
5