管理 iPhone、iPod touch 或 Apple Watch 上的“健康”數據
https://support.apple.com/zh-cn/HT204351
HealthKit 框架提供了一個結構,應用可以使用它來分享健康和健身數據。
HealthKit 管理從不同來源獲得的數據,并根據用戶的偏好設置,自動將不同來源的數據合并起來。應用還可以獲取每個來源的原始數據,然后執(zhí)行自己的數據合并。
HealthKit 也可以直接與健康和健身設備一起工作。在iOS8.0中,系統可以自動將兼容的低功耗藍牙心率儀的數據直接保存在HealthKit存儲中。若有M7運動協處理器(iphone5s以上設備才支持),系統可以自動導入計步數據。其他的設備和數據來源必須有配套的應用才可以獲取數據并保存在HealthKit中。
HealthKit 提供了一個健康應用來管理用戶的健康數據。用戶可通過健康應用來查看、添加、刪除或者管理其全部的健康和健身數據。用戶還可以編輯每種數據類型的分享權限。
HealthKit 和 健康應用在iPad上都不可用。
HealthKit 框架不能用于應用擴展。
HealthKit和隱私
由于健康數據可能是敏感的,HealthKit通過精確控制哪些信息允許應用讀取,從而讓用戶可以控制這些數據。用戶必須明確設置每個應用在HealthKit存儲中讀寫的權限。用戶可以單獨為每種數據類型設置準許或拒絕的權限。例如,用戶可以允許你的應用讀取計步數據,但是不允許讀取血糖水平。為了防止可能的信息泄露,應用是不知道它是否被禁止讀取數據的。從應用的角度來看,如果被禁止讀取數據,就是沒有那種類型的數據存在。
HealthKit的數據不會保存在iCloud中,也不會在多設備間同步。這些數據只會保存在用戶的本地設備中。為了安全考慮,當設備沒有解鎖時,HealthKit存儲的數據是加密的。
另外,你的應用如果主要不是提供健康或健身服務的話,那就不能調用HealthKit的API。如果你的應用提供健康和健身服務,就必須要在銷售文本和用戶界面上明確的表明。特別是下面幾條指導適用于所有HealthKit應用。
1. 你的應用不應該將HealthKit收集的數據用于廣告或類似的服務。注意,可能在使用HealthKit框架應用中還是要服務廣告,但是你不能使用HealthKit中的數據來服務廣告。
2. 在沒有用戶的明確允許下,你不能向第三方展示任何HealthKit收集的數據。即使用戶允許,你也只能向提供健康或健身服務的第三方展示這些數據。
3. 你不能將HealthKit收集的數據出售給廣告平臺、數據代理人或者信息經銷商。
4. 如果用戶允許,你可以將HealthKit數據共享給第三方用于醫(yī)學研究。
5. 你必須明確說明,你和你的應用會怎樣使用用戶的HealthKit數據。
你必須為每個使用HealthKit框架的應用提供一份隱私策略。你可以在以下網站找到創(chuàng)建隱私策略的指導:
1. Personal Health Record model (for non-HIPAA apps): http://www.healthit.gov/policy-researchers-implementers/personal-health-record-phr-model-privacy-notice?
2. HIPAA model (for HIPAA covered apps): http://www.hhs.gov/ocr/privacy/hipaa/modelnotices.html?
這些模板由ONC開發(fā),通過直白的語言和平易近人的設計來解釋用戶數據是如何被收集和共享的,從而提高用戶的體驗和理解。這些不是用來替代基本的互聯網隱私策略,而且開發(fā)者應該咨詢ONC來了解你的應用適用哪種模板。這些模板僅供參考,Apple不承擔你使用這些模板帶來的后果。
參看App Store Review Guidelines中的HealthKit章節(jié)來了解更多,可以在Apple的 App Review Support? 頁面中找到。關于使用敏感用戶數據的更多技術信息,參見 App Programming Guide for iOS? 中的 Best Practices for Maintaining User Privacy? 。
HealthKit的設計思路
HealthKit是用來在應用間以一種有意義的方式共享數據。為了達到這點,框架限制只能使用預先定義好的數據類型和單位。這些限制保證了其他應用能理解這些數據是什么意思已經怎樣使用。因此,開發(fā)者不能創(chuàng)建自定義數據類型和單位。而HealthKit盡量會提供一個應該完整的數據類型和單位。
框架還大量使用了子類化,在相似的類間創(chuàng)建層級關系。通常這些類間都有一些細微但是重要的差別。還有不少很相關的類,需要正確地區(qū)別開才能一起工作。例如HKObject ?和 HKObjectType?抽象類有很多平行層級的子類。當使用object和object type時,必須確保使用匹配的子類。
HealthKit中所有的對象都是HKObject的子類。大部分 HKObject 對象子類都是不可變的。每個對象都有下列屬性:
1.? UUID。每個對象的唯一標示符。
2.? Source。數據的來源。來源可以是直接把數據存進HealthKit的設備,或者是應用。當一個對象保存進HealthKit中時,HealthKit會自動設置其來源。只有從HealthKit中獲取的數據source屬性才可用。
3.? Metadata。一個包含關于該對象額外信息的字典。元數據包含預定義和自定義的鍵。預定義的鍵用來幫助在應用間共享數據。自定義的鍵用來擴展HealthKit對象類型,為對象添加針對應用的數據。
HealthKit對象主要分為2類:特征和樣本。特征對象代表一些基本不變的數據。包括用戶的生日、血型和生理性別。你的應用不能保存特征數據。用戶必須通過健康應用來輸入或者修改這些數據。
樣本對象代表某個特定時間的數據。所有的樣本對象都是HKSample?的子類。它們都有下列屬性:
1.? Type。樣本類型。例如,這可能包括一個睡眠分析樣本、一個身高樣本或者一個計步樣本。
2.? Start date。樣本的開始時間。
3.? End date。樣本的結束時間。如果樣本代表時間中的某一刻,結束時間和開始時間相同。如果樣本代表一段時間內收集的數據,結束時間應該晚于開始時間。
樣本可以細分為四個樣本類型。
1.? 類別樣本。這種樣本代表一些可以被分為有限種類的數據。在iOS8.0中,只有一種類別樣本,睡眠分析。更多信息,參見HKCategorySample Class Reference?。
2.? 數量樣本。這種樣本代表一些可以存儲為數值的數據。數量樣本是HealthKit中最常見的數據類型。這些包括用戶的身高和體重,還有一些其他數據,例如行走的步數,用戶的體溫和脈搏率。更多信息,參見HKQuantitySample Class Reference?。
3.? Correlation。這種樣本代表復合數據,包含一個或多個樣本。在iOS8.0中,HealthKit使用correlation來代表食物和血壓。在創(chuàng)建書屋或者血壓數據時,你應該使用correlation。更多信息,參見 HKCorrelation Class Reference?。
4.? Workout。Workout代表某些物理活動,像跑步、游泳,甚至游戲。Workout通常有類型、時長、距離、和消耗能量這些屬性。你還可以為一個workout關聯許多詳細的樣本。不像correlation,這些樣本是不包含在workout里的。但是,它們可以通過workout獲取到。更多信息,參見 HKWorkout Class Reference?。
設置HealthKit
在你開始使用HealthKit之前,必須要執(zhí)行下列步驟:
1. 在Xcode中打開HealthKit功能。
2. 調用 isHealthDataAvailable? 方法來查看HealthKit在該設備上是否可用。HealthKit在iPad上不可用。
3. 為你的應用實例化一個 HKHealthStore? 對象。每個應用只需要一個HealthKit存儲實例。這個存儲實例就是你和HealthKit數據庫交互的主要接口。
4. 使用 requestAuthorizationToShareTypes:readTypes:completion:?方法來請求獲取HealthKit數據的權限。對每種類型的數據,你都必須請求許可來共享和讀取。
如果用戶允許分享某種類型的數據,那么你可以創(chuàng)建這種類型的新樣本,并保存在HealthKit中。你應該使用 authorizationStatusForType:?來檢查是否允許分享這種類型的數據。
如果用戶允許讀取某種類型的數據,那么你就可以從HealthKit中讀取這些數據。不幸的是,即使知道用戶拒絕讀取某種類型的數據,也可能會顯示出潛在的健康問題。因此,你的應用無法確定用戶是否允許讀取數據。如果你沒有得到讀取某種數據的許可,那簡單來看就好像是HealthKit中沒有這種類型的數據。
關于設置HealthKit的更多內容,參見HKHealthStore Class Reference?。
有一個展示了如何設置和使用HealthKit的例子,參見Fit: Store and Retrieve HealthKit Data?。
向HealthKit 存儲中添加樣本
你的應用可以創(chuàng)建新的樣本,并添加到HealthKit存儲中。對所有樣本類型來說,大致的過程都是類似的,但是又有一些不同。
1.? 在 HealthKit Constants Reference? 中找到正確的類型標識符。
2.? 使用類型標識符創(chuàng)建一個匹配的 HKObjectType? 子類。有一些便捷的方法,參見? HKObjectType Class Reference?。
3.? 使用對象類型,創(chuàng)建一個匹配的 HKSample? 子類。
4.? 使用 saveObject:withCompletion:? 方法將對象保存到HealthKit 存儲中。
每個 HKSample? 子類都有自己的便捷方法來實例化樣本對象。流程如下圖所示。
對于數量樣本,你必須創(chuàng)建一個 HKQuantity?? 類的實例。數量的單位必須和類型標識符文檔中描述的可用單位相關。例如,HKQuantityTypeIdentifierHeight? 文檔中說明它使用長度單位,因此,你的數量必須使用厘米、米、英尺、英寸或者其他長度單位。更多信息,參見 HKQuantitySample Class Reference?。
對于類別樣本,它的值必須和類型標識符文檔中描述的枚舉值相關。例如, HKCategoryTypeIdentifierSleepAnalysis? 文檔中說明它使用 HKCategoryValueSleepAnalysis? 枚舉值。因此你在創(chuàng)建樣本時必須從這個枚舉中傳遞一個值。更多信息,參見 HKCategorySample Class Reference?。
對于correlation,你必須先創(chuàng)建correlation包含的所有樣本。correlation的類型標識符描述了它可以包含的類型和對象的數量。不要把被包含的對象存進HealthKit。它們是以correlation的一部分存儲的。更多信息,參見HKCorrelation Class Reference?。
workout和其他類型的樣本有些不同。首先,創(chuàng)建 HKWorkoutType? 實例并不需要指定類型標識符。所有的workout都是用同樣的類型標識符。第二,對于每個workout你都需要提供一個 HKWorkoutActivityType? 值。這個值定義了workout中執(zhí)行的活動的類型。最后,當workout保存到HealthKit后,你可以給workout關聯額外的樣本。這些樣本提供了workout的詳細信息。更多信息,參見HKWorkout Class Reference?。
讀取HealthKit數據
HealthKit提供了許多方法來讀取數據。
1.? 直接方法調用。HealthKit提供了直接讀取特征數據的方法。這些方法只能用于讀取特征數據。更多信息,參見 HKHealthStore Class Reference。
2.? 樣本查詢。這是使用最多的查詢。使用樣本查詢來讀取任何類型的樣本數據。當你想要對結果進行排序或者限制返回的樣本總數時,樣本查詢就特別有用。更多信息,參見 HKSampleQuery Class Reference?。
3.? 觀察者查詢。這是一個長時間運行的查詢,它會檢測HealthKit存儲,并在匹配到的樣本發(fā)生變化時通知你。如果當存儲發(fā)生變化時你想得到通知,就使用觀察者查詢。你可以讓這些查詢在后臺執(zhí)行。更多信息,參見HKObserverQuery Class Reference?。
4.? 錨定對象查詢。用這種查詢來搜索添加進存儲的項。當錨定查詢第一次執(zhí)行時,會返回存儲中所有匹配的樣本。在接下來的執(zhí)行中,只會返回上一次執(zhí)行之后添加的項目。通常,錨定對象查詢會和觀察者查詢一起使用。觀察者查詢告訴你某些項目發(fā)生了變化,而錨定對象查詢來決定有哪些(如果有的話)項目被添加進了存儲。更多信息,參見 HKAnchoredObjectQuery Class Reference?。
5.? 統計查詢。使用這種查詢來在一系列匹配的樣本中執(zhí)行統計運算。你可以使用統計查詢來計算樣本的總和、最小值、最大值或平均值。更多信息,參見 HKStatisticsQuery Class Reference?。
6.? 統計集合查詢。使用這種查詢來在一系列長度固定的時間間隔中執(zhí)行多次統計查詢。通常使用這種查詢來生成圖表。查詢提供了一些簡單的方法來計算某些值,例如,每天消耗的總熱量或者每5分鐘行走的步數。統計集合查詢是長時間運行的。查詢可以返回當前的統計集合,也可以監(jiān)測HealthKit存儲,并對更新做出響應。更多信息,參見 HKStatisticsCollectionQuery Class Reference?。
7.? Correlation查詢。使用這種查詢來在correlation查找數據。這種查詢可以為correlation中每個樣本類型包含獨立的謂詞。如果你只是想匹配correlation類型,那么請使用樣本查詢。更多信息,參見 HKCorrelation Class Reference?。
8.? 來源查詢。使用這種查詢來查找HealthKit存儲中的匹配數據的來源(應用和設備)。來源查詢會列出儲存的特定樣本類型的所有來源。更多信息,參見HKSourceQuery Class Reference?。
單位換算
HealthKit使用 HKUnit? 和 HKQuantity? 類來支持單位。HKUnit? 提供了單一單位的表示。它支持大部分的公制和英制單位,當然還包括基本單位和符合單位。基本單位代表單一的度量,例如米、磅或者秒。復合單位使用數學運算連接一個或多個基本單位,例如m/s或者lb/ft2。
HKUnit? 提供了便捷方法來創(chuàng)建HealthKit支持的所有基本單位。它還提供了構建復合單位需要的數學運算。最后,你還可以通過直接使用恰當的格式化的單位字符串來創(chuàng)建復合單位。
更多信息,參見HKUnit Class Reference?。
HKQuantity? 類存儲了給定單位的值。之后你可以用任何兼容的單位來取值。這樣,你的應用就可以很輕松的完成單位換算。
更多信息,參見 HKQuantity Class Reference?。
在某些場合,你可以使用格式化器來本地化數量。iOS8提供了提供了新的格式化器來處理長度(NSLengthFormatter)、質量(NSMassFormatter)和能量(NSEnergyFormatter)。對于其他的數量,你需要自己來換算單位和本地化數據。
多線程
HealthKit存儲是線程安全的,大部分HealthKit對象是不可變的。通常來說,你可以在多線程環(huán)境中安全地使用HealthKit。
注意:
所有HealthKit API的完成回調都在一個私有的后臺隊列中執(zhí)行。所以在你更新用戶界面或者修改一些只能在主線程中處理的資源之前,你應該把這些數據傳回主線程。
關于多線程和并發(fā)編程的更多信息,參見 Concurrency Programming Guide?。
添加數字簽名
HealthKit中的數字簽名元數據對象提供了由可信設備生成的樣本記錄的數據完整性。元數據項存儲了一份數字簽名過的樣本記錄副本。簽名是由設備生成的(由于設備存儲了私有簽名密鑰,所以設備應該是防篡改的)。這樣數據的使用者就可以通過設備的公鑰來檢查簽名,進而驗證記錄數據是否被修改過。由于每條記錄都是獨立簽名的,所以每條記錄總共能存儲大約1K bytes的簽名。因此,這種元數據簽名項是給那些樣本率最多在每天幾次的記錄使用的。更高的樣本率需要對樣本組進行簽名,這已經超出了本文檔的范圍。
通常,私鑰是在制造時置入防篡改的測量設備中的。(私鑰重置或證書更新的政策或機制不在本文檔的范圍內)相關的公鑰會由設備制造商公布,例如在它們的網站上。設備應該將每個樣本的樣本數據和簽名傳給iOS應用,這兩者都會儲存在HealthKit數據庫中。注意,數字簽名的公私鑰是用來提供數據完整性檢查的,不是用來加密的。數據記錄中的實際值都是明文。
數字簽名的格式是IETF RFC 5652中的CMS(Cryptographic Message Syntax)。簽名使用ASN.1的DER(Distinguished Encoding Rules)編碼。使用的信息摘要應該是SHA256,簽名加密應該是FIPS PUB 186-4 Digital Signature Standard Elliptic Curve P-256。這樣長度和效率都有保證。另外,整個簽名都應該使用bsae64編碼,這樣就能存儲在HealthKit NSString 元數據對象中。
簽名應該是ASN.1 Signed-data Content Type:
SignedData ::= SEQUENCE {? version CMSVersion,? digestAlgorithms DigestAlgorithmIdentifiers,? encapContentInfo EncasulatedContentInfo,? signerInfos SignerInfo }
SignerInfo Type是:
SignerInfo ::= SEQUENCE {? version CMSVersion,? sid SignerIdentifier,? digestAlgorithm DigestAlgorithmIdentifier,? signatureAlgorithem SignatureAlgorithmIdentifier,? signatureSignatureValue }
上面就是摘要和簽名的算法。可選項目都沒有包含在其中。SignerIdentifier用來取得適當的公鑰,來對簽名進行驗證。
EncapsulatedContentInfo是從由設備生成的樣本記錄中的相關項目的副本。副本應該用ASN.1 DER編碼,并應至少包含一個樣本時間戳和采樣值。將記錄數據拷貝進簽名內,是為了有一個穩(wěn)定的定義良好的二進制編碼(ASN.1 DER),這對于生成一個可驗證的簽名來說是必要的。ASN.1編碼的plist結構(鍵值對),應該能滿足大部分記錄類型。(參見“使用ASN.1 DER來編碼plist結構”)
#import "HealthManager.h"
#import "HealthModel.h"
@interface HealthManager ()
@property (nonatomic, strong) HKHealthStore *healthStore;
@end
static HealthManager *_share_HealthManager = nil;
@implementation HealthManager
+ (instancetype) shareHealthManager
{
if (_share_HealthManager == nil) {
_share_HealthManager = [[HealthManager alloc] init];
}
return _share_HealthManager;
}
#pragma mark - getter
- (HKHealthStore *)healthStore {
if (!_healthStore) {
_healthStore = [[HKHealthStore alloc] init];
}
return _healthStore;
}
+ (BOOL)isHealthDataAvailable {
return [HKHealthStore isHealthDataAvailable];
}
- (void)authorizateHealthKit:(void (^)(BOOL isAuthorizateSuccess))resultBlock {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//讀操作
NSSet *readObjectTypes = [NSSet setWithObjects:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount],[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned] ,nil];
//寫操作
/*心率,血糖,舒張壓 ,收縮壓*/
NSSet *writeObjectTypes = [NSSet setWithObjects:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate],[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodGlucose],[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureDiastolic],[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureSystolic], nil];
[self.healthStore requestAuthorizationToShareTypes:writeObjectTypes readTypes:readObjectTypes completion:^(BOOL success, NSError * _Nullable error) {
if (resultBlock) {
resultBlock(success);
}
}];
});
}
/*!
*
*
*? @brief? 獲取卡路里
*/
- (void)getKilocalorieUnitCompletionHandler:(void(^)(double value, NSError *error))handler
{
//? ? 查詢的基類是HKQuery,這是一個抽象類,能夠實現每一種查詢目標
//? ? 查詢采樣信息
HKSampleType *sampleType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:nil endDate:nil options:HKQueryOptionStrictStartDate];
//NSSortDescriptors用來告訴healthStore
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:HKSampleSortIdentifierStartDate ascending:NO];
HKSampleQuery *sampleQuery = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:predicate limit:HKObjectQueryNoLimit sortDescriptors:@[sortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
if(!error && results) {
HKQuantitySample *samples = results.firstObject;
HKQuantity *energyBurned = samples.quantity;
double value = [energyBurned doubleValueForUnit:[HKUnit kilocalorieUnit]];
if(handler)
{
handler(value,error);
}
}
else
{
if(handler)
{
double value = 0.0;
handler(value,error);
}
}
}];
//執(zhí)行查詢
[self.healthStore executeQuery:sampleQuery];
//? ? HKQuantityType *quantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
//
//? ? ? ? HKStatisticsQuery *query = [[HKStatisticsQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:nil options:HKStatisticsOptionCumulativeSum completionHandler:^(HKStatisticsQuery *query, HKStatistics *result, NSError *error) {
//? ? ? ? ? ? HKQuantity *sum = [result sumQuantity];
//
//
//
//
//? ? ? ? ? ? double value = [sum doubleValueForUnit:[HKUnit kilocalorieUnit]];
//
//? ? ? ? ? ? if(handler)
//? ? ? ? ? ? {
//? ? ? ? ? ? ? ? handler(value,error);
//? ? ? ? ? ? }
//? ? ? ? }];
//
//? ? [self.healthStore executeQuery:query];
}
- (void)readStepCount:(void (^)(NSString *stepCount))LatestStepCountResultBlock
{
/**********************************
查詢全部時間段的步數
**********************************/
//查詢的基類是HKQuery,這是一個抽象類,能夠實現每一種查詢目標
//查詢采樣信息
//? ? HKSampleType *sampleType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
//? ? NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:nil endDate:nil options:HKQueryOptionStrictStartDate];
//? ? //NSSortDescriptors用來告訴healthStore
//? ? NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:HKSampleSortIdentifierStartDate ascending:NO];
//? ? HKSampleQuery *sampleQuery = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:predicate limit:HKObjectQueryNoLimit sortDescriptors:@[sortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
//? ? ? ? if(!error && results) {
//? ? ? ? ? ? for(HKQuantitySample *samples in results) {
//? ? ? ? ? ? ? ? NSLog(@"%@ 至 %@ : %@", samples.startDate, samples.endDate, samples.quantity);
//? ? ? ? ? ? }
//? ? ? ? }
//? ? ? ? else
//? ? ? ? {
//
//? ? ? ? }
//
//? ? }];
//? ? //執(zhí)行查詢
//? ? [self.healthStore executeQuery:sampleQuery];
/**********************************
查詢按天查詢的步數
**********************************/
[self fetchAllHealthDataByDay:^(NSArray *modelArray)
{
dispatch_async(dispatch_get_main_queue(), ^{
if (modelArray.count > 0) {
HealthModel *healthModel = (HealthModel *)modelArray.firstObject;
//NSString *result = [NSString stringWithFormat:@"%@,%ld",[NSString stringWithFormat:@"%zd年%zd月%zd日",healthModel.startDateComponents.year,healthModel.startDateComponents.month,healthModel.startDateComponents.day],(long)healthModel.stepCount];
NSString *result = [NSString stringWithFormat:@"%ld",(long)healthModel.stepCount];
LatestStepCountResultBlock(result);
}
else
{
NSString *result = @" 0";
LatestStepCountResultBlock(result);
}
});
//? ? ? ? for ( HealthModel *healthModel in modelArray) {
//
//? ? ? ? ? ? self.stepLabel.text = F(@"%@,%ld",F(@"%zd年%zd月%zd日", healthModel.startDateComponents.year, healthModel.startDateComponents.month, healthModel.startDateComponents.day),healthModel.stepCount);
//? ? ? ? }
}];
}
- (void)fetchAllHealthDataByDay:(void (^)(NSArray *modelArray))queryResultBlock {
HKQuantityType *quantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
NSDateComponents *intervalComponents = [[NSDateComponents alloc] init];
intervalComponents.day = 1;
__block NSCalendar *calendar;
#ifdef IOS8
calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
#else
calendar = [NSCalendar calendarWithIdentifier:NSGregorianCalendar];
#endif
NSDateComponents *currentComponents = [calendar components:NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour fromDate:[NSDate date]];
NSDate *endDate = [NSDate dateWithTimeIntervalSinceNow: - (currentComponents.hour * 3600 + currentComponents.minute * 60 + currentComponents.second)];
NSDateComponents *anchorComponents = [calendar components:NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour | NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear
fromDate:endDate];
[self executeQueryForQuantityType:quantityType
predicate:nil
anchorDate:[calendar dateFromComponents:anchorComponents]
intervalComponents:intervalComponents
callBackResult:^(HKStatisticsCollection * _Nullable result, NSError *error) {
if (error) {
NSLog(@"an error occurred while calculating the statistics %@",error.localizedDescription);
} else {
__block NSMutableArray *tempArray = @[].mutableCopy;
[result.statistics enumerateObjectsUsingBlock:^(HKStatistics * _Nonnull statistics, NSUInteger idx, BOOL * _Nonnull statisticsStop) {
[statistics.sources enumerateObjectsUsingBlock:^(HKSource * _Nonnull source, NSUInteger idx, BOOL * _Nonnull sourceStop) {
if ([source.name isEqualToString:[UIDevice currentDevice].name]) {//只取設備的步數,過濾其他第三方應用的
double stepCount = [[statistics sumQuantityForSource:source] doubleValueForUnit:[HKUnit countUnit]];
@autoreleasepool {
//數據封裝
HealthModel *healthModel = [[HealthModel alloc] init];
healthModel.startDateComponents = [calendar components:NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour | NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear
fromDate:statistics.startDate];
healthModel.endDateComponents = [calendar components:NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour | NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear
fromDate:statistics.endDate];
healthModel.stepCount = stepCount;
[tempArray insertObject:healthModel atIndex:0];//倒序
}
*sourceStop = YES;
}
}];
}];
if (queryResultBlock) {
queryResultBlock(tempArray);
}
}
}];
}
//查詢
- (void)executeQueryForQuantityType:(HKQuantityType *)quantityType
predicate:(nullable NSPredicate *)quantitySamplePredicate
anchorDate:(NSDate *)anchorDate
intervalComponents:(NSDateComponents *)intervalComponents
callBackResult:(void (^)(HKStatisticsCollection * __nullable result, NSError *error))queryResult {
HKStatisticsCollectionQuery *collectionQuery =
[[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType
quantitySamplePredicate:quantitySamplePredicate
options:HKStatisticsOptionCumulativeSum | HKStatisticsOptionSeparateBySource
anchorDate:anchorDate
intervalComponents:intervalComponents];
collectionQuery.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection * __nullable result, NSError * __nullable error){
if (queryResult) {
queryResult(result, error);
}
};
[self.healthStore executeQuery:collectionQuery];
}
-(void)saveBloodDataToHealthstoreWithData:(BloodDataModel *)dataModel
{
HKQuantitySample *HeartRateSample,*BloodGlucoseSample,*BloodPressureDiastolicSample,*BloodPressureSystolicSample;
HKCorrelation? *BloodPressureCorrelation;
NSMutableArray *saveTypesArray = [[NSMutableArray alloc] init];
if (dataModel.HeartRate) {
HKUnit *HeartRate = [HKUnit unitFromString:@"count/min"];
HeartRateSample = [HKQuantitySample quantitySampleWithType:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate] quantity:[HKQuantity quantityWithUnit:HeartRate doubleValue:[NSNumber numberWithInteger:dataModel.HeartRate].doubleValue] startDate:dataModel.date endDate:dataModel.date];
[saveTypesArray addObject:HeartRateSample];
}
if (dataModel.BloodGlucose) {
BloodGlucoseSample = [HKQuantitySample quantitySampleWithType:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodGlucose] quantity:[HKQuantity quantityWithUnit:[HKUnit unitFromString:@"mg/dL"]doubleValue:dataModel.BloodGlucose*18.0] startDate:dataModel.date endDate:dataModel.date];
[saveTypesArray addObject:BloodGlucoseSample];
}
if (dataModel.BloodPressureDiastolic && dataModel.BloodPressureSystolic) {
BloodPressureDiastolicSample = [HKQuantitySample quantitySampleWithType:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureDiastolic] quantity:[HKQuantity quantityWithUnit:[HKUnit millimeterOfMercuryUnit] doubleValue:dataModel.BloodPressureDiastolic] startDate:dataModel.date endDate:dataModel.date];
BloodPressureSystolicSample = [HKQuantitySample quantitySampleWithType:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureSystolic] quantity:[HKQuantity quantityWithUnit:[HKUnit millimeterOfMercuryUnit] doubleValue:dataModel.BloodPressureSystolic] startDate:dataModel.date endDate:dataModel.date];
BloodPressureCorrelation = [HKCorrelation correlationWithType:[HKCorrelationType correlationTypeForIdentifier:HKCorrelationTypeIdentifierBloodPressure] startDate:dataModel.date endDate:dataModel.date objects:[NSSet setWithObjects:BloodPressureDiastolicSample,BloodPressureSystolicSample, nil]];
[saveTypesArray addObject:BloodPressureCorrelation];
}
[self.healthStore saveObjects:saveTypesArray withCompletion:^(BOOL success,NSError * _Nullable error)
{
if (error!= nil) {
NSLog(@"Error saving sample:%@",error.localizedDescription);
}
else
{
NSLog(@"Sample saved successfully!");
}
}];
}
@end