Swift: HealthKitに体温データを入力する。できるだけ公式ドキュメントだけを見て。


まずは公式ドキュメントをちゃんと読む人間になろうと思いたち、Apple公式ドキュメントだけを元にHealthKitにアクセスを試みました。

環境

  • XCode Version 13.0
  • iOS target 14.0

参考(公式)

プロジェクト設定

HealthKitの有効化(プロジェクト設定の「Signing & Capabilities」にHealthKitを追加

アクセス要求用のメッセージ定義

今回は体温データの入力のみなのでPrivacy - Health Update Usage Description
アプリが読み出しする場合はPrivacy - Health Share Usage Descriptionが必要になります

参考: Protecting User Privacy (developer.apple.com)

これらのDescriptionは13文字以上必要なようです。これに関しては調査力が足りず、StackOverflowのお世話になりました。 https://stackoverflow.com/questions/37863093/exception-nsinvalidargumentexception-nshealthupdateusagedescritption

コード

今回は1つのクラス内でHealthKitへの参照を完結させます

setup()

HKHealthStoreはドキュメントによれば、無闇に生成せず保持し続けるのがいいようです。

You need only a single HealthKit store per app. These are long-lived objects; you create the store once, and keep a reference for later use.

Setting Up HealthKit (developer.apple.com)

エラーチェックで呼び出し側の処理を変えることなども考慮し、setup()メソッドでStoreの生成を行います。(エラーチェックが不要であればinit()内で生成してました)

postBodyTemperature()

以下の順番で処理を行います

  • 体温データに関するアクセス許可取得
  • アクセス許可状態の確認
  • 体温データの保存

出来上がったClass

import Foundation
import HealthKit

enum BodyTemperatureUnit{
    /// 摂氏
    case degreeCelsius
    /// 華氏
    case degreeFahrenheit
}
class HealthCareRepository{
    let allTypes = Set([HKObjectType.quantityType(forIdentifier: .bodyTemperature)!])
    /// HKHealthStoreはアプリケーションあたり1インスタンス。1回生成したらそれを使い続ける必要あり
    var store:HKHealthStore? = nil

    func setup() -> Bool{
        /// ipadではヘルスケア使えない
        /// https://developer.apple.com/documentation/healthkit/setting_up_healthkit
        /// Ensure HealthKit’s Availability
        if (HKHealthStore.isHealthDataAvailable() == false){
            // ヘルスデータが無効状態
            return false
        }

        /// ヘルスケア機能があり、有効である場合生成する
        self.store = HKHealthStore()
        return true
    }

    func postBodyTemperature(_ value:Double, unit:BodyTemperatureUnit, completion:@escaping (Bool, Error?) -> Void) -> Void{

        /// https://developer.apple.com/documentation/healthkit/authorizing_access_to_health_data
        /// Request Permission from the User
        /// toShare: Write要求
        /// read: Read要求
        self.store!.requestAuthorization(toShare: allTypes, read: nil){ (success, error) in
            if !success{
                completion(success, error)
                return
            }

            /// https://developer.apple.com/documentation/healthkit/authorizing_access_to_health_data
            /// Check for Authorization Before Saving Data
            let status = self.store!.authorizationStatus(for: .quantityType(forIdentifier: .bodyTemperature)!)
            switch status{
            case .notDetermined:
                // "If you have not yet requested permission"
                // ここに入ることはないはず
                print("Not determined")
                completion(false, HKError(HKError.errorAuthorizationNotDetermined))
                return
            case .sharingDenied:// If the user has denied permission
                // ユーザーが許可しなかった場合
                print("Sharing Denied")
                completion(false, HKError(HKError.errorAuthorizationDenied))
                break
            case .sharingAuthorized:
                // ユーザーが許可した場合
                print("Sharing Authorized")
                break
            @unknown default:
                print("Unknown status.")
                break
            }

            // Datetime
            let now = Date()
            // 摂氏 or 華氏
            let hkUnit:HKUnit
            switch unit {
            case .degreeCelsius:
                hkUnit = .degreeCelsius()
            case .degreeFahrenheit:
                hkUnit = .degreeFahrenheit()
            }

            let quantity = HKQuantity(unit: hkUnit, doubleValue: value)
            let obj = HKQuantitySample(type: .quantityType(forIdentifier: .bodyTemperature)!, quantity: quantity, start: now, end: now)
            self.store!.save(obj, withCompletion: completion)
        }
    }
}

結果

適当なUI作って上記クラスを試した結果、シミュレータ上ではありますが無事に体温データをヘルスケアに登録することができました。大抵のことは公式Documentに書いてあることも実感できました。次回はUI予定です。