[iOS] HealthKit 獲取步數
前言
HealthKit 是蘋果在 iOS 8.0 之后推出的健康框架,HealthKit框架提供了一個結構,應用可以使用它來分享健康和健身數據。
設計思路
HealthKit 是用來在應用間以一種有意義的方式共享數據。為了達到這點,框架限制只能使用預先定義好的數據類型和單位。
框架還大量使用了子類化,在相似的類間創建層級關系。通常這些類間都有一些細微但是重要的差別。還有不少很相關的類,需要正確地區別開才能一起工作。例如 HKObject 和 HKObjectType抽象類有很多平行層級的子類。當使用 object 和 object type 時,必須確保使用匹配的子類。
HKObject
HealthKit中所有的對象都是HKObject的子類。
大部分 HKObject 對象子類都是不可變的。每個對象都有下列屬性:
- UUID。每個對象的唯一標示符。
- Source Revision。數據的來源。來源可以是直接把數據存進HealthKit的設備,或者是應用。當一個對象保存進HealthKit中時,HealthKit會自動設置其來源。只有從HealthKit中獲取的數據source屬性才可用。
- Metadata。一個包含關于該對象額外信息的字典。元數據包含預定義和自定義的鍵。預定義的鍵用來幫助在應用間共享數據。自定義的鍵用來擴展HealthKit對象類型,為對象添加針對應用的數據。
- Device. 在此 sampler 生成的數據存儲硬件設備。
HKSample
HealthKit對象主要分為2類:特征和樣本。特征對象代表一些基本不變的數據。包括用戶的生日、血型和生理性別。你的應用不能保存特征數據。用戶必須通過健康應用來輸入或者修改這些數據。所有的樣本對象都是HKSample的子類。它們都有下列屬性:
- Type。 // 樣本類型。例如,這可能包括一個睡眠分析樣本、一個身高樣本或者一個計步樣本。
- Start date。// 樣本的開始時間。
- End date。 // 樣本的結束時間。如果樣本代表時間中的某一刻,結束時間和開始時間相同。如果樣本代表一段時間內收集的數據,結束時間應該晚于開始時間。
HKObjectType 含有一些樣本類型
HKCategorySample 類別樣本。這種樣本代表一些可以被分為有限種類的數據。在iOS8.0中,只有一種類別樣本,睡眠分析。更多信息,參見 HKCategorySample Class Reference。
HKQuantitySample 數量樣本。這種樣本代表一些可以存儲為數值的數據。數量樣本是HealthKit中最常見的數據類型。這些包括用戶的身高和體重,還有一些其他數據,例如行走的步數,用戶的體溫和脈搏率。更多信息,參見HKQuantitySample Class Reference。
Correlation 符合數據。這種樣本代表復合數據,包含一個或多個樣本。在iOS8.0中,HealthKit使用 correlation來代表食物和血壓。在創建書屋或者血壓數據時,你應該使用 correlation。更多信息,參見 HKCorrelation Class Reference。
Workout。Workout 代表某些物理活動,像跑步、游泳,甚至游戲。Workout 通常有類型、時長、距離、和消耗能量這些屬性。你還可以為一個 workout 關聯許多詳細的樣本。不像 correlation,這些樣本是不包含在 workout 里的。但是,它們可以通過 workout 獲取到。更多信息,HKWorkout Class Reference
設置HealthKit
- 要在Xcode中打開HealthKit功能。
// 打開之后,在 Bundle 里會多一個 .entitlements 文件。
-
調用 isHealthDataAvailable 方法來查看HealthKit在該設備上是否可用。
if ([HKHealthStore isHealthDataAvailable]) { // add code to use HealthKit here... }
-
為你的應用實例化一個 HKHealthStore 對象。每個應用只需要一個HealthKit存儲實例。這個存儲實例就是你和HealthKit數據庫交互的主要接口。
self.healthStore = [[HKHealthStore alloc] init];
-
使用 requestAuthorizationToShareTypes:readTypes:completion:方法來請求獲取HealthKit數據的權限。對每種類型的數據,你都必須請求許可來共享和讀取。
// 設置共享數據類型 HKQuantityType *sampleType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]; NSSet *shareSet = [NSSet setWithObjects:sampleType, nil]; // 設置寫入數據類型 HKQuantityType *read = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]; NSSet *readSet = [NSSet setWithObjects:read, nil]; [self.healthStore requestAuthorizationToShareTypes:shareSet readTypes:readSet completion:^(BOOL success, NSError * _Nullable error) { if (success) { NSLog(@" 請求權限成功"); } else{ NSLog(@"error %@", error); } }];
讀取HealthKit數據
有三種主要的方式來訪問數據從HealthKit存儲:
- 直接方法調用。HealthKit 提供了直接讀取特征數據的方法。這些方法只能用于讀取特征數據。HKHealthStore
-
查詢
樣本查詢(Sample query )。這是使用最多的查詢。使用樣本查詢來讀取任何類型的樣本數據。當你想要對結果進行排序或者限制返回的樣本總數時,樣本查詢就特別有用。HKSampleQuery
固定對象查詢。用這種查詢來搜索添加進存儲的項。當錨定查詢第一次執行時,會返回存儲中所有匹配的樣本。在接下來的執行中,只會返回上一次執行之后添加的項目。通常,錨定對象查詢會和觀察者查詢一起使用。觀察者查詢告訴你某些項目發生了變化,而錨定對象查詢來決定有哪些(如果有的話)項目被添加進了存儲。HKAnchoredObjectQuery
統計查詢。使用這種查詢來在一系列匹配的樣本中執行統計運算。你可以使用統計查詢來計算樣本的總和、最小值、最大值或平均值。HKStatisticsQuery
統計集合查詢。使用這種查詢來在一系列長度固定的時間間隔中執行多次統計查詢。通常使用這種查詢來生成圖表。查詢提供了一些簡單的方法來計算某些值,例如,每天消耗的總熱量或者每5分鐘行走的步數。統計集合查詢是長時間運行的。查詢可以返回當前的統計集合,也可以監測HealthKit存儲,并對更新做出響應。HKStatisticsCollectionQuery
Correlation 查詢。使用這種查詢來在 correlation 查找數據。這種查詢可以為 correlation 中每個樣本類型包含獨立的謂詞。如果你只是想匹配 correlation 類型,那么請使用樣本查詢。 HKCorrelation
來源查詢。使用這種查詢來查找 HealthKit 存儲中的匹配數據的來源(應用和設備)。來源查詢會列出儲存的特定樣本類型的所有來源。HKSourceQuery
活動匯總查詢。 使用這個可以查詢用戶活動的總結信息。每個活動匯總對象包含給定天數的用戶活動總結,你可以查詢一天或者多天。更多信息見,HKActivitySummaryQuery
-
長時間運行的查詢。 這些查詢隊列繼續運行在一個匿名的后臺線程中,并且無論何時改變了 HealthKit 數據,都能更新其應用。此外,觀察者查詢能注冊到后臺中。因而能使得在更新的時候,HealthKit 能在后臺喚醒你的 app。
-
觀察者查詢。這是一個長時間運行的查詢,它會檢測HealthKit存儲,并在匹配到的樣本發生變化時通知你。如果當存儲發生變化時你想得到通知,就使用觀察者查詢。你可以讓這些查詢在后臺執行。HKObserverQuery
每種數據調用的查詢的方法不一樣,類型也要相應的匹配。
NSDate *startDate, *endDate;
endDate = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *now = [NSDate date];
startDate = [calendar startOfDayForDate:now];
endDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:1 toDate:startDate options:0];
// NSLog(@"startDate %@, endDate %@", startDate, now);HKSampleType *sampleType = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]; // 創建query, 設置 start 和 end 謂詞 Create a predicate to set start/end date bounds of the query NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate]; // Create a sort descriptor for sorting by start date NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:HKSampleSortIdentifierStartDate ascending:YES]; __weak UILabel *numberLabel = self.showView.existStpNumberLabel; HKSampleQuery *sampleQuery = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:predicate limit:HKObjectQueryNoLimit sortDescriptors:@[sortDescriptor] resultsHandler:^(HKSampleQuery * _Nonnull query, NSArray<__kindof HKSample *> * _Nullable results, NSError * _Nullable error) { if(!error && results){ CGFloat sum = 0.0f; for(HKQuantitySample *sample in results) { // NSLog(@"%@", sample); HKUnit *unit = [HKUnit unitFromString:@"count"]; CGFloat number = [sample.quantity doubleValueForUnit:unit]; sum += number; } dispatch_async(dispatch_get_main_queue(), ^{ numberLabel.text = @(sum).description; }); } }]; [self.healthStore executeQuery:sampleQuery];
-
向 HealthKit Store 中添加樣本
你需要創建一個新的樣本對象,并將它添加到實例化的 Health Store 中去。樣本類型都是相似的類型,只是在主體上可能有一些變化。
在 HealthKit Constants Reference 中找到正確的類型標識符。HealthKit
使用類型標識符創建一個匹配的 HKObjectType 子類。有一些便捷的方法,參見 HKObjectType Class Reference。
使用對象類型,創建一個匹配的 HKSample 子類。
-
使用 saveObject:withCompletion: 方法將對象保存到HealthKit 存儲中。
對于數量樣本,你必須創建一個 HKQuantity 類的實例。數量的單位必須和類型標識符文檔中描述的可用單位相關。例如,HKQuantityTypeIdentifierHeight 文檔中說明它使用長度單位,因此,你的數量必須使用厘米、米、英尺、英寸或者其他長度單位。HKQuantitySample
對于類別樣本,它的值必須和類型標識符文檔中描述的枚舉值相關。例如, HKCategoryTypeIdentifierSleepAnalysis 文檔中說明它使用 HKCategoryValueSleepAnalysis 枚舉值。因此你在創建樣本時必須從這個枚舉中傳遞一個值。HKCategorySample
對于correlation,你必須先創建correlation包含的所有樣本。correlation的類型標識符描述了它可以包含的類型和對象的數量。不要把被包含的對象存進HealthKit。它們是以correlation的一部分存儲的。 HKCorrelation
workout和其他類型的樣本有些不同。首先,創建 HKWorkoutType 實例并不需要指定類型標識符。所有的workout都是用同樣的類型標識符。第二,對于每個workout你都需要提供一個 HKWorkoutActivityType 值。這個值定義了workout中執行的活動的類型。最后,當workout保存到HealthKit后,你可以給workout關聯額外的樣本。這些樣本提供了workout的詳細信息。HKWorkout
性別,年齡, NSError *error; HKBiologicalSexObject *bioSex = [healthStore biologicalSexWithError:&error]; switch (bioSex.biologicalSex) { case HKBiologicalSexNotSet: // undefined break; case HKBiologicalSexFemale: // ... break; case HKBiologicalSexMale: // ... break; } 體重 // Some weight in gram double weightInGram = 83400.f; // Create an instance of HKQuantityType and // HKQuantity to specify the data type and value // you want to update NSDate *now = [NSDate date]; HKQuantityType *hkQuantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass]; HKQuantity *hkQuantity = [HKQuantity quantityWithUnit:[HKUnit gramUnit] doubleValue:weightInGram]; // Create the concrete sample HKQuantitySample *weightSample = [HKQuantitySample quantitySampleWithType:hkQuantityType quantity:hkQuantity startDate:now endDate:now]; // Update the weight in the health store [healthStore saveObject:weightSample withCompletion:^(BOOL success, NSError *error) { // .. }]; // 獲取步數 NSDate *eDate = [NSDate date]; NSDate *sDate = [NSDate dateWithTimeInterval:-300 sinceDate:eDate]; HKQuantity *stepQuantityConsumed = [HKQuantity quantityWithUnit:[HKUnit countUnit] doubleValue:self.showView.addStepNumberTextField.text.doubleValue]; HKQuantityType *stepConsumedType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]; NSString *strName = [[UIDevice currentDevice] name]; NSString *strModel = [[UIDevice currentDevice] model]; NSString *strSysVersion = [[UIDevice currentDevice] systemVersion]; NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier]; HKDevice *device = [[HKDevice alloc] initWithName:strName manufacturer:@"Apple" model:strModel hardwareVersion:strModel firmwareVersion:strModel softwareVersion:strSysVersion localIdentifier:localeIdentifier UDIDeviceIdentifier:localeIdentifier]; HKQuantitySample *stepConsumedSample = [HKQuantitySample quantitySampleWithType:stepConsumedType quantity:stepQuantityConsumed startDate:sDate endDate:eDate device:device metadata:nil]; [self.healthStore saveObject:stepConsumedSample withCompletion:^(BOOL success, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if (success) { [self.view endEditing:YES]; UIAlertView *doneAlertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"添加成功" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil]; [doneAlertView show]; //刷新數據 重新獲取步數 [self getTodyStepNumberForHealthKit]; }else { NSLog(@"The error was: %@.", error); UIAlertView *doneAlertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"添加失敗" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil]; [doneAlertView show]; return ; } }); }];
單位換算
- HealthKit使用 HKUnit 和 HKQuantity 類來支持單位。HKUnit 提供了單一單位的表示。它支持大部分的公制和英制單位,當然還包括基本單位和符合單位。基本單位代表單一的度量,例如米、磅或者秒。復合單位使用數學運算連接一個或多個基本單位,例如m/s或者lb/ft2。
HKUnit 提供了便捷方法來創建HealthKit支持的所有基本單位。它還提供了構建復合單位需要的數學運算。最后,你還可以通過直接使用恰當的格式化的單位字符串來創建復合單位。
HKQuantity 類存儲了給定單位的值。之后你可以用任何兼容的單位來取值。這樣,你的應用就可以很輕松的完成單位換算。
在某些場合,你可以使用格式化器來本地化數量。iOS8提供了提供了新的格式化器來處理長度(NSLengthFormatter)、質量(NSMassFormatter)和能量(NSEnergyFormatter)。對于其他的數量,你需要自己來換算單位和本地化數據