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

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

# 環境

* XCode Version 13.0
* iOS target 14.0

# 参考(公式）

* 前説
    * https://developer.apple.com/documentation/healthkit/about_the_healthkit_framework
* XCodeでHealthKitを有効化する
    * https://developer.apple.com/documentation/healthkit/setting_up_healthkit
* プライバシーデータへアクセスするためのプロジェクト設定
    * https://developer.apple.com/documentation/healthkit/protecting_user_privacy
* Swiftでヘルスケアデータにアクセス
    * https://developer.apple.com/documentation/healthkit/authorizing_access_to_health_data

# プロジェクト設定

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2088399/02d24ed8-eb10-b007-a468-c2d5cde13584.png)


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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2088399/465eee4a-8a13-3778-bd86-ffb0117e59d3.png)


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

参考: [Protecting User Privacy (developer.apple.com)](https://developer.apple.com/documentation/healthkit/protecting_user_privacy)

:::note info
これらのDescriptionは１３文字以上必要なようです。これに関しては調査力が足りず、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)](https://developer.apple.com/documentation/healthkit/setting_up_healthkit)


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

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

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

## 出来上がったClass

```swift
import Foundation
import HealthKit

enum BodyTemperatureUnit{
    /// 摂氏
    case degreeCelsius
    /// 華氏
    case degreeFahrenheit
}
class HealthCareRepository{
    let allTypes = Set([HKObjectType.quantityType(forIdentifier: .bodyTemperature)!])
    /// HKHealthStoreはアプリケーションあたり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予定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2088399/3e4d3f4a-c05a-c2c6-98ec-51bd21a79249.png)