Интеграция HealthKit для доступа к данным здоровья в iOS
HealthKit — это не просто API чтения данных с Apple Watch. Это центральное хранилище здоровья iOS с жёсткой схемой данных, гранулярными разрешениями на каждый тип данных и политикой App Store, нарушение которой приводит к отклонению приложения. Интеграция занимает больше времени, чем кажется: не из-за сложности API, а из-за количества edge-cases с разрешениями и правилами использования данных.
Разрешения и App Store Review
Apple проверяет HealthKit-интеграцию вручную при каждом ревью. Основные причины отклонения:
- Приложение запрашивает типы данных, которые не использует (
HKObjectTypeдолжны соответствовать реальной функциональности) - Нет
NSHealthShareUsageDescription/NSHealthUpdateUsageDescriptionвInfo.plist— банальный crash при первом запросе - Приложение запрашивает разрешения на запись тренировок, но само не является fitness-приложением — отклонение по Guideline 5.1.1 (Privacy)
Особенность разрешений HealthKit: пользователь может запретить доступ к определённому типу, но приложение никогда не узнает об этом явно. HKHealthStore.authorizationStatus(for:) возвращает .notDetermined и при запрете, и при «ещё не спрашивали». Это защита приватности — по состоянию разрешения нельзя сделать вывод о том, есть ли у пользователя эти данные.
Практическое следствие: нельзя показывать алерт «вы не дали доступ к шагам». Надо молча пробовать читать данные, и если массив пустой — показывать нейтральное сообщение «данные недоступны» с кнопкой «Открыть Здоровье».
Чтение данных: HKSampleQuery vs 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
// workout сохранён в HealthKit
}
}
Типичные ошибки
- Вызов HealthKit API в MainActor без
async/await— заблокирует UI на медленных запросах к большим наборам данных - Не проверять
HKHealthStore.isHealthDataAvailable()— на iPad без Apple Watch HealthKit недоступен - Читать ЧСС в единицах
count/minвместоHKUnit(from: "count/min")— результат будет неверным
Сроки
Базовая интеграция чтения шагов, ЧСС и тренировок — 5–8 рабочих дней. С записью тренировок, фоновой синхронизацией и экраном разрешений — 2–3 недели.







