HealthKit実装メモ
iOSアプリでHealthKitを利用する際のメモ。 初期設定を中心に記載する。
XCodeのバージョンは15.0.1
を利用した。
プロジェクトの設定
プロジェクトごとにXCodeで設定が必要なため、以下の設定を行う。
- アプリ設定の
Signing & Capabilities
でHealthKit
を追加する Info
のCustom iOS Target Properties
にヘルスケアデータの利用文を設定する- 読み込み:
Privacy - Health Share Usage Description
- 書き込み:
Privacy - Health Update Usage Description
- 読み込み:
ヘルスケアデータの初期化
今回はテストを簡単にしたいため、Serviceクラスを作成して、HealthKitのデータを取得するようにする。
ヘルスケアデータを利用する際はHKUnit
と呼ばれる計測値の単位が必要なため、取得したいデータの単位を設定する。
また、サービス開始時にユーザーに許可を求めるため、requestAuthorization
を利用する。
class HealthService: ObservableObject {
let healthStore: HKHealthStore
let metsUnit = HKUnit.kilocalorie().unitDivided(by: HKUnit.gramUnit(with: .kilo)).unitDivided(by: HKUnit.hour())
init() {
self.healthStore = HKHealthStore()
let mets = HKQuantityType(.physicalEffort)
let healthTypes: Set = [mets]
Task {
do {
try await self.healthStore.requestAuthorization(toShare: [], read: healthTypes)
} catch {
print("failed to authorization.")
}
}
}
}
作成したサービスは各Viewの中でEnvironmentObjectとして利用する。 直接インスタンス化するとプレビューでクラッシュしてしまうが、適切にモックに差し替えることで、クラッシュせずにプレビューができる。
インスタンス化は@main
で行う。
@main
struct HealthApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(HealthService())
}
}
}
Viewの中で利用する際は以下のように記述する。
struct HealthView: View {
@EnvironmentObject var healthService: HealthService
}
データの取得
データの取得にはHealthKitのクエリを利用する。
func getRawMets(date: Date = Date()) async -> [MetsRawData] {
let pyhsicalEffortType = HKQuantityType(.physicalEffort)
let startDay = date.diff(days: 0)
let tomorrow = date.diff(days: 1)
// Create the predicate for the query
let predicate = HKQuery.predicateForSamples(withStart: startDay, end: tomorrow, options: .strictStartDate)
return await withCheckedContinuation{continuation in
let query = HKSampleQuery(sampleType: pyhsicalEffortType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { (query, samples, error) in
if let error = error {
print("Error: \(error.localizedDescription)")
continuation.resume(returning: [])
} else {
guard let quantitySamples = samples as? [HKQuantitySample] else {
print("No data available")
return continuation.resume(returning: [])
}
var ret: [MetsRawData] = []
// Process the samples
for sample in quantitySamples {
let mets = sample.quantity.doubleValue(for: self.metsUnit)
if (3 < mets) {
let second = sample.endDate.timeIntervalSince(sample.startDate)
let methHour = mets * second / 3600
ret.append(MetsRawData(startDate: sample.startDate, endDate: sample.endDate, mets: methHour))
}
}
continuation.resume(returning: ret)
}
}
// Execute the query
healthStore.execute(query)
}
}
データの書き込み
TODO: あとで書く
プレビューでのモック差し替え
XCodeのプレビューではヘルスケアデータが取得できないため、アプリがクラッシュする。 そのため、プレビュー時にはHealthKitを利用しないモックデータを差し込むようにする。
モックは作成したHealthServiceを継承して、メソッドをオーバーライドする。
class HealthServiceMock: HealthService {
override init() {}
override func getRawMets(date: Date) async -> [MetsRawData] {
return []
}
}
作成したモックはプレビューでは以下のように注入すればよい。
#Preview {
WeekView()
.environmentObject(HealthServiceMock() as HealthService)
}