HeathKit是Apple公司在推出iOS 8 系統時一塊推出的關於健康信息的框架。如果iPhone手機系統升級到iOS8之後就會發現多了一個健康-app,這就是Apple提供的一個記錄用戶健康信息的app,可以用它來分享健康和健身數據。還可以指定數據的來源,比如我們自己創建一個app,在我們的app中使用了HeathKit框架之後只要經過用戶的認證,就可以在我們的app之中給健康分享數據或者從健康中獲取數據。
HeathKit可以與健身設備一起工作,iPhone手機自身可以監控步數信息,會自動導入步數信息。但是其他信息或者設備需要配套的應該才能獲取到數據並導入到HeathKit中並在健康中顯示。
HeathKit不能再iPad中使用,而且它也不支持擴展。
由於用戶的健康信息可能是敏感的,所以這些用戶信息不能讓開發者很隨便的獲取到。每條信息的讀寫都需要用戶去選擇是否同意,比如用戶可以同意你獲取到用戶的身高體重,但是不同意讀寫生殖健康等其他用戶不願意公開的信息。為了防止信息洩露,我們是不知道用戶是否禁止了某條信息是否被用戶禁止讀取的。簡單的說,如果獲取不到某條信息,就代表沒有這條信息。
關於更多的關於隱私的信息,可以參考隱私
HeathKit在各個應用之間提供了一種有意義的方式共享數據。因此,我們必須使用HeathKit框架提供的數據類型和單位。這保證了數據存在的真正意義,我們不能自定義數據類型及單位。框架使用了子類化,例如HKObject和HKObjectType抽象類擁有很多有平行關系的子類,當使用Object或者ObjectType的時候,必須確保使用正確的子類。
在HeathKit中能夠存儲的類都是HKObject的子類,大部分HKObject的子類都是不可變的。每個對象都有下面的屬性:
UUID:每個對象的標識符Source:數據的來源,來源可以是HeathKit的健康app,也可以是我們自己創建的app。當一個對象存儲到HeathKit中時會設置其來源。只有從HeathKit中獲取到的數據的來源才有效。Metadata:一個包含該對象額外信息的字典,元數據包含預定義的key和自定義的key,預定義的key用來幫助我們在應用間共享數據,而自定義的key用來擴展HeathKit,為對象添加針對應用的數據。HeathKit的對象主要分為特征和樣本。特征對象代表用戶的基本不變的數據,包括用戶的生日、血型和性別等。我們創建的app不能修改這些信息,只能讓用戶在健康中去修改或者添加個人特征信息。
樣本對象代表某個特定時間的數據,所有的樣本類型的對象都是HKSample的子類。它們都有下面的特性:
樣本類型又可以分為四個類型:
類別樣本(HKCategorySample):在iOS 8 中,只有睡眠分析這一個類別樣本。代表有限種類的樣本.數量樣本(HKQuantitySample):這種樣本代表存儲數據的樣本,比如步數、距離、用戶的體溫等。它是HeathKit中最常見的數據類型。關系樣本(HKCorrelation):代表復合數據,包括一個或者多個樣本。在iOS 8 中,用correlation代表食物和血壓。在創建食物或血壓時,需要用correlation。訓練活動(HKWorkout):代表某種活動,比如走、跑步等。包含有開始時間、結束時間、運動類型、消耗能量、運動距離等屬性。還可以為workout關聯許多詳細的樣本。不像correlation,這些樣本不包含在workout中,但是可以通過workout獲取到。再介紹一個HeathKit中經常用到的一些類。
每個HkSample的子類都有對應的便利方法創建對應的對象。比如:
對於數量樣本,需要創建HKQuantity類的實例。而且數量的單位和類型標識符文檔中描述的可用單位要相同。例如:HKQuantityTypeIdentifierHeight 文檔中說明它使用長度單位,因此,你的數量必須使用厘米、米、英尺、英寸或者其他長度單位。
對應類別樣本,需要創建HKCategorySample的實例。它的值必須和類型標識符文檔中描述的枚舉值相關。例如, HKCategoryTypeIdentifierSleepAnalysis 文檔中說明它使用的枚舉值。因此你在創建樣本時必須從這個枚舉中傳遞一個值。
同樣,你必須先創建correlation包含的所有樣本。correlation的類型標識符描述了它可以包含的類型和對象的數量。不要把被包含的對象存進HealthKit。它們是以correlation的一部分存儲的。
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrbU09rRtcG3u+62r9H5sb6jrMrXz8ijrLS0vaggSEtXb3Jrb3V0VHlwZSDKtcD9sqKyu9Do0qrWuLaowODQzbHqyra3+6Gjy/nT0LXEd29ya291dLa8ysfTw82s0fm1xMDg0M2x6sq2t/uho7Xatv6jrLbU09rDv7j2d29ya291dMTjtrzQ6NKqzOG5qdK7uPYgSEtXb3Jrb3V0QWN0aXZpdHlUeXBlINa1oaPV4rj21rW2qNLlwct3b3Jrb3V01tDWtNDQtcS77ravtcTA4NDNoaPX7rrzo6y1sXdvcmtvdXSxo7Tmtb1IZWFsdGhLaXS686OsxOO/ydLUuPh3b3Jrb3V0udjBqrbuzeK1xNH5sb6ho9Xi0KnR+bG+zOG5qcHLd29ya291dLXEz+rPuNDFz6KhozwvcD4NCjxwPjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160921/20160921092807350.png" title="\" />
HeathKit提供了許多查詢讀取數據的方法:
直接方法查詢。對於特征樣本,可以直接查詢獲取到,這些方法只能查詢特征樣本。更多信息: HKHealthStore Class Reference樣本查詢。這是使用最多的查詢。使用樣本查詢可以查詢在HeathKit中任意的數據。而且可以對結果進行排序等。更多信息:HKSampleQuery Class Reference
觀察者查詢。這是一個長時間運行的查詢,它會檢測HealthKit存儲,並在匹配到的樣本發生變化時通知你。如果當存儲發生變化時你想得到通知,就使用觀察者查詢。更多信息:HKObserverQuery Class Reference
錨定對象查詢。用這種查詢來搜索添加進存儲的項。當錨定查詢第一次執行時,會返回存儲中所有匹配的樣本。在接下來的執行中,只會返回上一次執行之後添加的項目。通常,錨定對象查詢會和觀察者查詢一起使用。觀察者查詢告訴你某些項目發生了變化,而錨定對象查詢來決定有哪些(如果有的話)項目被添加進了存儲。更多信息:HKAnchoredObjectQuery Class Reference統計查詢。使用這種查詢來在一系列匹配的樣本中執行統計運算。你可以使用統計查詢來計算樣本的總和、最小值、最大值或平均值。更多信息: HKStatisticsQuery Class Reference
統計集合查詢。使用這種查詢來在一系列長度固定的時間間隔中執行多次統計查詢。通常使用這種查詢來生成圖表。查詢提供了一些簡單的方法來計算某些值,例如,每天消耗的總熱量或者每5分鐘行走的步數。統計集合查詢是長時間運行的。查詢可以返回當前的統計集合,也可以監測HealthKit存儲,並對更新做出響應。更多信息,參見 HKStatisticsCollectionQuery Class Reference。
Correlation查詢。使用這種查詢來在correlation查找數據。這種查詢可以為correlation中每個樣本類型包含獨立的謂詞。如果你只是想匹配correlation類型,那麼請使用樣本查詢。更多信息,參見 HKCorrelation Class Reference。
來源查詢。使用這種查詢來查找HealthKit存儲中的匹配數據的來源(應用和設備)。來源查詢會列出儲存的特定樣本類型的所有來源。更多信息,參見HKSourceQuery Class Reference。
這個類代表要查詢的數據的單位的類,比如體重的單位,可以為kg、lbs等。這個類為不同的數據類型提供了不同的單位方法。一般在創建前面介紹的樣本類型的時候,都需要這個類為樣本添加對應的單位。而且提供了一些數學運算,比如千米、米、厘米等之間的轉換。
在某些場合,你可以使用格式化器來本地化數量。iOS8提供了提供了新的格式化器來處理長度(NSLengthFormatter)、質量(NSMassFormatter)和能量(NSEnergyFormatter)。對於其他的數量,你需要自己來換算單位和本地化數據。
HeathKit的核心就是它,它代表HeathKit的數據庫,使用它就可以從數據庫中讀取數據。比較重要的方法:
isHealthDataAvailable:判斷當前設置是否支持HeathKitrequestAuthorizationToShareTypes(typesToShare: Set?, readTypes typesToRead: Set?, completion: (Bool, NSError?) -> Void): 向用戶請求同意讀寫某些數據saveObject(object: HKObject, withCompletion completion: (Bool, NSError?) -> Void) :向數據庫中添加數據executeQuery(query: HKQuery) :執行查詢,即上面介紹的幾種查詢方法。在使用HealthKit之前,必須要執行下列步驟:
打開HeathKit,在Target欄中,打開Capabilities菜單,將HealthKit這一項的開關設為ON的狀態。創建HeathManager.Swift 文件,並導入
`import HeathKit`
HeathKit的核心是HeathStore,創建
func authorizeHealthKit(completion:((success:Bool,error:NSError!)->Void)!){}
然後調用在這個方法中調用isHealthDataAvailable判斷當前設備是否支持HeathKit
//判斷當前設備是否支持 if !HKHealthStore.isHealthDataAvailable(){ let error = NSError(domain: "", code: 2, userInfo: [NSLocalizedDescriptionKey:"HealthKit is not available in this Device"]) if completion != nil { completion(success: false, error: error) } }
,最後在上面的方法中,設置要讀寫的數據類型。
為你的應用實例化一個 HKHealthStore 對象。每個應用只需要一個HealthKit存儲實例。這個存儲實例就是你和HealthKit數據庫交互的主要接口。
let hkHealthStore = HKHealthStore()
使用 requestAuthorizationToShareTypes:readTypes:completion:來認證請求從HeathKit獲取數據的權限。
//請求連接 hkHealthStore.requestAuthorizationToShareTypes(healthKitTypesToWrite as? Set, readTypes: healthKitTypesToRead as? Set ) { (success, error) -> Void in if completion != nil{ completion(success:success,error:error) } return }
如果當前設備支持HeathKit的時候,這樣就會彈出一個請求界面,讓用戶選擇是否同意你能夠獲取到你要請求的數據。
我們首先創建了ProfileViewController.swift,並用IB創建一個請求個人信息的界面
然後在HeathManager.Swift 文件中添加請求個人信息的方法。
對於請求特征信息,前提上用戶通過健康添加了出生日期、性別、血型等特征信息
func readProfile()->(age:Int?,biologicalsex:HKBiologicalSexObject?,bloodType:HKBloodTypeObject?){ //請求年齡 var age:Int? let birthDay:NSDate; do { birthDay = try hkHealthStore.dateOfBirth() let today = NSDate() let diff = NSCalendar.currentCalendar().components(.Year, fromDate: birthDay, toDate: today, options: NSCalendarOptions(rawValue: 0)) age = diff.year }catch { } //請求性別 var biologicalSex :HKBiologicalSexObject? do { biologicalSex = try hkHealthStore.biologicalSex() }catch { } //請求血型 var hkbloodType:HKBloodTypeObject? do { hkbloodType = try hkHealthStore.bloodType() }catch{ } return (age,biologicalSex,hkbloodType) }
請求體重、身高、BMI的時候,創建另外的方法。
func fetchMostRecentSample(sample:HKSampleType,competion:((HKSample!,NSError!)->Void)!){ //1.創建謂詞 let past = NSDate.distantPast() let now = NSDate() let mostRecentPredicate = HKQuery.predicateForSamplesWithStartDate(past, endDate: now, options: .None) //2.創建返回結果排序的描述,是降序還是升序的,因為只需要一個結果,就設定限制為1個 let sortDescrptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate , ascending: false) let limit = 1 //3.創建HKSampleQuery對象, let sampleQuery = HKSampleQuery(sampleType: sample, predicate: mostRecentPredicate, limit: limit, sortDescriptors: [sortDescrptor]) { (sampleQuery, results, error) -> Void in if let queryError = error { competion(nil,queryError) return } let mostRecentSample = results?.first if competion != nil{ competion(mostRecentSample,nil) } } //4.執行查詢 self.hkHealthStore.executeQuery(sampleQuery) }
獲取之後在之前創建的ProfileViewController.swift文件中獲取這些信息,並更新UI。
對應特征信息,可以直接調用查詢方法,並更新
let profile = healthManager?.readProfile() self.healthStore = HKHealthStore() ageLabel.text = profile?.age == nil ? kUnKnowString:String(profile!.age!) sexLabel.text = biologicSexLiteral(profile?.biologicalsex?.biologicalSex) bloodTypeLabel.text = bloodTypeLiteral(profile?.bloodType?.bloodType)
這裡面創建了兩個工具方法biologicSexLiteral和bloodTypeLiteral來修改查詢的結果為我們想要的樣子並顯示在界面上。
對於體重和身高,需要創建樣本查詢
/** 獲取並更新體重 */ func updateWeight(){ let weightSampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass) self.healthManager?.fetchMostRecentSample(weightSampleType!, competion: { (mostRecentSample, error) -> Void in if error != nil { return } var weightString = self.kUnKnowString self.weight = mostRecentSample as? HKQuantitySample //根據我們想要的數據類型單位獲取對應的結果 if let kilograms = self.weight?.quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(HKMetricPrefix.Kilo)){ //體重格式化 let weightFommater = NSMassFormatter() weightFommater.forPersonMassUse = true weightString = weightFommater.stringFromKilograms(kilograms) } //因為這個查詢默認是異步查詢的,所以需要在主線程更新UI dispatch_async(dispatch_get_main_queue()) { () -> Void in self.weightLabel.text = weightString self.updateBMILabel() } }) } /** 獲取並更新身高 */ func updateHeight(){ //設置要查找的類型,根據標識符 let heightSampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight) //獲取身高樣本 self.healthManager?.fetchMostRecentSample(heightSampleType!, competion: { (heightSample, error) -> Void in if error != nil { return } var heightStr = self.kUnKnowString self.height = heightSample as? HKQuantitySample //根據我們想要的數據類型單位獲取對應的結果 if let kilograms = self.height?.quantity.doubleValueForUnit(HKUnit.meterUnit()){ heightStr = String(format: "%.2f", kilograms) + "m" } //因為這個查詢默認是異步查詢的,所以需要在主線程更新UI dispatch_async(dispatch_get_main_queue()) { () -> Void in self.heightLabel.text = heightStr self.updateBMILabel() } }) }
對應BMI,它代表人的身體質量指數,它的計算方式是:體重/(身高*身高)。因此它可以這樣獲得
/** 獲取並設置BMI: */ func updateBMILabel(){ //根據我們想要的數據類型單位獲取對應的結果 let weight = self.weight?.quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(HKMetricPrefix.Kilo)) let height = self.height?.quantity.doubleValueForUnit(HKUnit.meterUnit()) var bmiValue = 0.0 if height == 0{ return } dispatch_async(dispatch_get_main_queue()) { () -> Void in bmiValue = (weight!)/(height! * height!) self.BMILabel.text = String(format: "%.02f", bmiValue) } }
在下面的方法中添加一個alertView讓用戶輸入BMI值,然後點擊確認按鈕之後添加到HeathStore中
@IBAction func addBMIData2HealthStore(sender: AnyObject) { let alertView = UIAlertController(title: "輸入BMI值", message: nil, preferredStyle: .Alert) alertView.addTextFieldWithConfigurationHandler { (textField) -> Void in textField.keyboardType = .NumberPad } let action = UIAlertAction(title: "添加", style: .Default) { (action) -> Void in var value:Double? if let text = alertView.textFields?.first?.text { if text.characters.count > 0 { value = Double(text) self.saveBMI2HealthStore(value!) } } } alertView.addAction(action) self .presentViewController(alertView, animated: true, completion: nil) } //保存BMI到heathKitStore中 func saveBMI2HealthStore(height:Double){ //BMI的類型 let BMIType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMassIndex) //根據標識符對應的單位創建BMI的數量對象 let BMIQuantity = HKQuantity(unit: HKUnit.countUnit(), doubleValue: height) let now = NSDate() //根據起止時間以及上面創建的創建HKQuantity對象創建數量樣本 let BMISample = HKQuantitySample(type: BMIType!, quantity: BMIQuantity, startDate: now, endDate: now) //保存數量樣本到healthStore中 self.healthStore?.saveObject(BMISample, withCompletion: { (success, error) -> Void in if success { print("添加成功") self.updateWeight() } if (error != nil) { print("添加失敗") } }) }
如果添加成功,你就可以去手機上的健康查找BMI,就可以看到我們剛才添加的BMI值,而且它的來源是我們創建的app。
創建一個WorkOutsViewController.swift文件,並在SB中拖對應的IB文件,界面如下
然後在在HeathManager.Swift 文件中添加請求workout的方法
/** 獲取workoutData */ func fetchWorkOutsData(completion:([AnyObject]!,NSError!)->Void){ let workOutsSampleType = HKSampleType.workoutType() let workOutsPredicate = HKQuery.predicateForWorkoutsWithWorkoutActivityType(.Running) let sortDescrptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate , ascending: false) let workOutsQuery = HKSampleQuery(sampleType: workOutsSampleType, predicate: workOutsPredicate, limit: 0, sortDescriptors: [sortDescrptor]) { (workoutsQuery, results, error) -> Void in if (error != nil){ print("獲取失敗") return } if results != nil{ completion(results!,nil) } } self.hkHealthStore.executeQuery(workOutsQuery) }
然後在WorkOutsViewController.swift文件的viewWillAppear()方法中請求workout
self.healthManager?.fetchWorkOutsData({ (results, error) -> Void in if error != nil{ print("獲取失敗") }else{ self.workOuts = results as! [HKWorkout] } dispatch_async(dispatch_get_main_queue(), { () -> Void in self.tableView.reloadData() }); })
最後在tableView顯示如下
在上面的界面的NavgationBar的rightBarItem向下再拖一個控制器,並添加對應的文件AddWorkoutsViewController.swift,並在IB中設置界面信息如下
然後在 AddWorkoutsViewController.swift 復寫 tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath),針對點擊不同的cell,執行不同的方法。即讓用戶輸入點擊的cell對應的輸入方式,比如時間就是時間選擇器。距離就是一個警示框加一個文本框等。
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { tableView.deselectRowAtIndexPath(indexPath, animated: true) self.tableView.tableFooterView = UIView() switch indexPath.row{ case 0: self.setupPickerView() case 1,2: self.setupDatePickerView(indexPath.row) case 3,4: self.setupAlertView(indexPath.row) default: break } }
這裡面對應的選擇的方法就不一一介紹了,就是幾個普通的view的添加。添加完所有的信息之後就可以點擊done 保存信息,方法如下:
@IBAction func addWorkOut(sender: AnyObject) { self.heathStore = HKHealthStore() //獲取距離和能量的數值 let distanceValue = Double(self.distanceLabel.text!) let energyValue = Double(self.energyLabel.text!) //根據上面的數值創建對應的HKQuantity對象 let distance = HKQuantity(unit: HKUnit.mileUnit(), doubleValue: distanceValue!) let energy = HKQuantity(unit: HKUnit.calorieUnit(), doubleValue: energyValue!) let endDate = self.dateFommater?.dateFromString(self.endDateLabel.text!) let startDate = self.dateFommater?.dateFromString(self.startDateLabel.text!) //這裡我默認設置成running了。可以根據具體的類型再進行設置。 //創建HKWorkout對象。 let workout = HKWorkout(activityType: .Running, startDate: startDate!, endDate: endDate!, workoutEvents: nil, totalEnergyBurned: energy, totalDistance: distance, metadata: nil) //保存上面創建的HKWorkout對象 self.heathStore?.saveObject(workout, withCompletion: { (success, error) -> Void in if error != nil{ print("添加錯誤") return } if success{ print("添加成功") dispatch_async(dispatch_get_main_queue(), { () -> Void in self.dismissViewControllerAnimated(true, completion: nil) }) } }) }
如果上面都執行成功,AddWorkoutsViewController.swift就會模態消失,然後在上面的一個頁面`WorkOutsViewController.swift就會在tableView的最上層顯示出我們剛才添加成功的HKWorkout
在本人過完春節回到公司上班之後經理問我健康app裡面的信息能不能獲取到。之前只是簡單了解了這個框架,但是裡面的具體結構體系並不了解。就趁著項目不忙,抽空把HeathKit學習了解了一下。本文的demo也采用了之前自學的Swift簡單的實現了一下(屬於Switer新手)。可能會有錯誤或不准確的地方,如果你看到了,可以給我聯系[email protected],我會及時更改的。寫這篇文章一是對HeathKit的學習的一個練習,在這也是給以後會用到的童鞋一個可以參考的東西。
HeathKit不只是上面的這些內容,但是能把上面的這些問題搞定,我覺得針對HeathKit的體系會有一個清楚的認識,學習HeathKit更深層次的內容會有很大的幫助。
本文的demo已經放到github上面,需要的同學可以下載看看。