שילוב HealthKit: נתוני בריאות ואימונים ב-iOS
אנו מפתחים אפליקציות iOS עם שילוב HealthKit, והנה הבעיה האמיתית שכמעט כל לקוח נתקל בה: לאחר הגרסה הראשונה, האפליקציה נדחית ב-App Store עקב בקשות הרשאות שגויות או הפרה של הנחיה 5.1.1. כ-30% מהאפליקציות המשתמשות ב-HealthKit עוברות את הבדיקה רק בניסיון השני. HealthKit אינו רק API לקריאת נתונים מ-Apple Watch—זוהי מערכת הבריאות המרכזית של iOS עם סכמה נוקשה, הרשאות גרעיניות לכל סוג, ומדיניות מחמירה מ-HealthKit.
במהלך השנים, השלמנו יותר מ-50 שילובי HealthKit עבור לקוחות בתחומי כושר, רפואה וביטוח. המהנדסים שלנו פיתחו רשימת בדיקה שמקצרת את זמן אישור ה-App Store בממוצע בשבועיים.
כיצד בדיקת App Store משפיעה על שילוב HealthKit
אפל בודקת ידנית כל שילוב HealthKit בכל בדיקה. הסיבות העיקריות לדחייה:
- האפליקציה מבקשת סוגי נתונים שאינה משתמשת בהם (
HKObjectTypeחייב להתאים לפונקציונליות בפועל). - חסרים
NSHealthShareUsageDescription/NSHealthUpdateUsageDescriptionב-Info.plist—קריסה טריוויאלית בבקשה הראשונה. - האפליקציה מבקשת הרשאת כתיבה לאימונים אך אינה אפליקציית כושר—דחייה תחת פרטיות (סעיף 5.1.1).
מוזרות בהרשאות HealthKit: המשתמש יכול לדחות גישה לסוג מסוים, אך האפליקציה לעולם לא לומדת על כך במפורש. HKHealthStore.authorizationStatus(for:) מחזיר .notDetermined גם כשנדחתה וגם כשעדיין לא נשאלה. זהו אמצעי הגנה על פרטיות—לא ניתן להסיק על קיום נתונים ממצב ההרשאה.
ההשלכה המעשית: לעולם אל תציג הודעה כמו "דחית גישה לנתוני צעדים." במקום זאת, נסה בשקט לקרוא את הנתונים, ואם המערך ריק, הצג הודעה ניטרלית "הנתונים אינם זמינים" עם כפתור "פתח את Health."
מדוע בחירת סוג השאילתה היא קריטית
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:
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)HKSampleQuery מעבד נתונים מצטברים פי 10 מהר יותר מאשר 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 } } :
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 מיועד לעדכוני רקע: האפליקציה מקבלת רק את ההפרש מאז השאילתה האחרונה.
| סוג שאילתה | מטרה | ביצועים |
|---|---|---|
| HKSampleQuery | דגימות גולמיות | בינוני (מוגבל בזיכרון) |
| HKStatisticsQuery | נתונים מצטברים (סכום, ממוצע) | גבוה (פי 10 מהר יותר) |
| HKAnchoredObjectQuery | עדכוני הפרש | גבוה (רק נתונים חדשים) |
כיצד להקליט אימון: HKWorkoutBuilder בזמן אמת
להקלטת אימון פעיל—השתמש תמיד ב-HKWorkoutBuilder, לא ב-HKWorkout(activityType:start:end:) הישן. הבונה מאפשר הוספת דגימות בזמן אמת:
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
- קריאה ל-HealthKit API על ה-main actor ללא
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 } }—חוסם את ממשק המשתמש בשאילתות איטיות על מערכי נתונים גדולים. - אי בדיקת
async/await—HealthKit אינו זמין ב-iPad ללא Apple Watch. - קריאת דופק ביחידות
HKHealthStore.isHealthDataAvailable()במקוםcount/min—התוצאות יהיו שגויות.
רשימה מלאה של סוגי נתוני HealthKit שאנו עובדים איתם
- צעדים (stepCount) ומרחק (distanceWalkingRunning)
- דופק (heartRate) ושונות (heartRateVariabilitySDNN)
- אנרגיה במנוחה ופעילה (basalEnergyBurned, activeEnergyBurned)
- שינה (sleepAnalysis)—קטגוריות: inBed, asleep, awake
- משקל, גובה, מדד מסת גוף
- גלוקוז בדם, לחץ דם, חמצן בדם
- אימונים עם מטא-דאטה: סוג, משך, קלוריות
מה כלול בעבודה: תוצרים
- קוד שילוב לקריאה וכתיבה של סוגי הנתונים הנדרשים.
- מסך בקשת הרשאות עם טקסט אינפורמטיבי.
- טיפול בכל מקרי הקצה (אין נתונים, דחייה, תוצאות ריקות).
- סנכרון רקע עם השרת באמצעות
HKUnit(from: "count/min"). - תיעוד על עבודה עם HealthKit עבור הצוות שלך.
- ייעוץ למעבר בדיקת App Store.
לוחות זמנים משוערים
| תרחיש | לוח זמנים |
|---|---|
| קריאת צעדים, דופק ואימונים | 5–8 ימי עסקים |
| הקלטת אימון + סנכרון רקע | 2–3 שבועות |
| מחזור מלא (קריאה, כתיבה, מסך הרשאות, פריסה) | החל מ-3 שבועות |
העלות נקבעת באופן אישי לאחר ניתוח הפרויקט שלך. הזמינו ייעוץ—נעריך את ההיקף ונכין הצעה מסחרית. צרו קשר כדי לדון בפרטי שילוב HealthKit באפליקציה שלכם. אנו מבטיחים אישור בדיקת App Store.







