Інтеграція HealthKit для доступу до даних здоров'я в iOS
HealthKit — це не просто API читання даних з Apple Watch. Це центральне сховище здоров'я iOS з жорсткою схемою даних, гранулярними дозволами на кожний тип даних та політикою App Store, порушення якої призводить до відхилення приложення. Інтеграція займає більше часу, ніж здається: не через складність API, а через кількість edge-cases з дозволами та правилами використання.
Дозволи та App Store Review
Apple перевіряє HealthKit-інтеграцію вручну при кожному review. Основні причини відхилення:
- Приложення запитує типи даних, які не використовує (
HKObjectTypeповинні відповідати реальній функціональності) - Немає
NSHealthShareUsageDescription/NSHealthUpdateUsageDescriptionвInfo.plist— банальний крах при першому запиті - Приложення запитує дозволи на запис тренувань, але саме по собі не є фітнес-приложенням — відхилення по Guideline 5.1.1 (Privacy)
Особливість дозволів HealthKit: користувач може заборонити доступ до певного типу, але приложення ніколи не дізнається про це явно. HKHealthStore.authorizationStatus(for:) повертає .notDetermined та при заборісті, та при «ще не питали». Це захист приватності — за станом дозволу неможна зробити висновок про те, чи є у користувача ці дані.
Практичне наслідок: не можна показати алерт «ви не дали доступ до кроків». Потрібно мовчазно пробувати читати дані, та якщо масив пустий — показувати нейтральне повідомлення «дані недоступні» з кнопкою «Відкрити Здоров'я».
Читання даних: HKSampleQuery проти HKStatisticsQuery
HKSampleQuery повертає сирі семпли — кожне вимірювання пульсу, кожну запис глюкози, кожний крок з кожного джерела. На активному користувачу за рік накопичуються десятки тисяч записів. Запит без ліміту та сортування — OutOfMemory або просто довге очікування.
let query = HKSampleQuery(
sampleType: HKQuantityType(.heartRate),
predicate: HKQuery.predicateForSamples(
withStart: startDate,
end: endDate,
options: .strictStartDate
),
limit: 1000,
sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)]
) { _, samples, error in
guard let samples = samples as? [HKQuantitySample] else { return }
let bpmValues = samples.map {
$0.quantity.doubleValue(for: .init(from: "count/min"))
}
// обробка
}
healthStore.execute(query)
Для агрегованих даних — HKStatisticsQuery або HKStatisticsCollectionQuery. Останній дозволяє отримати статистику по інтервалам (день, тиждень) за період — кроки за кожний день місяця одним запитом:
let interval = DateComponents(day: 1)
let query = HKStatisticsCollectionQuery(
quantityType: HKQuantityType(.stepCount),
quantitySamplePredicate: nil,
options: .cumulativeSum,
anchorDate: Calendar.current.startOfDay(for: Date()),
intervalComponents: interval
)
query.initialResultsHandler = { _, results, _ in
results?.enumerateStatistics(from: startDate, to: endDate) { stat, _ in
let steps = stat.sumQuantity()?.doubleValue(for: .count()) ?? 0
}
}
HKAnchoredObjectQuery — для фонових оновлень: приложення отримує тільки дельту з момента останнього запита. Використовуємо для синхронізації нових тренувань з сервером.
Запис тренувань: HKWorkoutBuilder
Для запису активної тренування — тільки HKWorkoutBuilder, не старий HKWorkout(activityType:start:end:). Builder дозволяє додавати семпли в реальному часі:
let config = HKWorkoutConfiguration()
config.activityType = .running
config.locationType = .outdoor
let builder = HKWorkoutBuilder(healthStore: healthStore, configuration: config, device: .local())
builder.beginCollection(withStart: Date()) { success, error in
// тренування почалася
}
// кожні 5 секунд додаємо ЧСС
let heartRateSample = HKQuantitySample(
type: HKQuantityType(.heartRate),
quantity: HKQuantity(unit: .init(from: "count/min"), doubleValue: 142),
start: Date(), end: Date()
)
builder.add([heartRateSample]) { _, _ in }
// завершення
builder.endCollection(withEnd: Date()) { _, _ in
builder.finishWorkout { workout, error in
// тренування збережена в HealthKit
}
}
Типові помилки
- Виклик HealthKit API в MainActor без
async/await— заблокує UI на повільних запитах до великих наборів даних - Не перевіряти
HKHealthStore.isHealthDataAvailable()— на iPad без Apple Watch HealthKit недоступен - Читати ЧСС в одиницях
count/minзамістьHKUnit(from: "count/min")— результат буде неверним
Терміни
Базова інтеграція чтення кроків, ЧСС та тренувань — 5–8 робочих днів. З записом тренувань, фоновою синхронізацією та екраном дозволів — 2–3 тижні.







