看新聞我們也知道,比起歷史上任何一個時刻,健身和健康在今天都更加重要。說起來也挺好笑的,我似乎記得幾天前新聞也在說同樣的事情,也許是因為年紀越來越大的緣故,我更需要健康和健身。不管怎麼說,這是一個熱門話題。隨著技術的不斷進步,手機應用和硬件在世界范圍內都變得流行起來,這些都給日益流行的健身健康話題加入了新的元素。
HealthKit 是蘋果公司的重要橋梁,把追蹤的重要的健康數據同有健康意識的科技消費者、運動迷、平常使用 iPhone 的人連接了起來。這很酷,用戶可以很容易的就追蹤衡量一段時間內的健身和健康數據,除了意識到的好處之外,我們看到圖標中向上走的曲線,就能給我們極大的鼓勵,激勵我們繼續運動。
正如我們能想象到的,在管理健康信息時,數據安全成為非常重要的因素。HealthKit 對於所有的 HealthKit 信息有絕對的控制權,會直接傳遞到用戶手中。用戶可以准許或者拒絕任何 App 獲取他們的健康數據的請求。
對於開發者來說,我們需要請求許可方能讀取或者寫入 HealthKit 數據。實際上,我們需要特別聲明一下,我們想影響獲取具體哪些數據。另外,任何使用 HealthKit 的 App 必須要包含一份 Privacy Policy(隱私協議),這樣用戶在進行信息交易時會覺得更舒服一些。
關於走路一小時(OneHourWalker)
今天,我們要創建一個非常有趣的 App,既能讀取 HealthKit 中的信息,也能寫入新的數據。看一下 OneHourWalker 的外表吧:
OneHourWalker 是一個健身 App,能夠跟蹤用戶在一個小時內走路或跑步的距離。用戶可以把距離分享到 HealthKit,這樣就能在健康應用中查看。我知道,整整一個小時聽起來確實有點嚇人,至少對我而言是這樣。因此,用戶可以提前結束健身,此時仍然可以分享距離。
所以,聽起來只需要把數據寫入 HealthKit 即可。不過我們要讀取的數據是什麼?
好問題!我喜歡在樹林裡的小路上漫步。我常常穿越一些枝杈縱橫的區域。因為我是八尺大漢,這會帶來一些問題。我們的解決方案是:我們會從 HealthKit 中讀取用戶的身高,然後顯示到 Label 控件上。這樣會比較友好地提示用戶,幫他避免不適合運動的區域。
下面是 OneHourWalker 的初始工程,下載然後運行,看起來好像 App 可以運行。計時器和定位系統都已經在運行了,所以我們只需要將注意力放在使用 HealthKit 上,注意一下,六十分鐘後,計時器和定位系統就會自動停止。
啟用 HealthKit
第一步就是在應用中開啟 HealthKit 功能,在 Project Navigator 中,選中 OneHourWalker,然後點擊 Targets 下方的 OneHourWalker。接著,在屏幕上方的 tab 欄中點擊 Capabilities。
在 Capabilities 底部把 HealthKit 設置為 On。這會把 HealthKit entitlement 添加到 App ID 中、把 HealthKit key 添加到 info plist 文件中、把 HealthKit entitlement 添加到資格文件中、連接 HealthKit.framework。就是這麼簡單。
開始寫代碼吧
找到 TimerViewController.swift,下面我們給 OneHourWalker 添加 HealthKit。首先我們創建一個 HealthKitManager 實例。
import UIKit import CoreLocation import HealthKit class TimerViewController: UIViewController, CLLocationManagerDelegate { @IBOutlet weak var timerLabel: UILabel! @IBOutlet weak var milesLabel: UILabel! @IBOutlet weak var heightLabel: UILabel! var zeroTime = NSTimeInterval() var timer : NSTimer = NSTimer() let locationManager = CLLocationManager() var startLocation: CLLocation! var lastLocation: CLLocation! var distanceTraveled = 0.0 let healthManager:HealthKitManager = HealthKitManager()
HealthKitManager.swift 裡包含了所有和 HealthKit 有關的操作。裡面有一些重要的方法,稍後我們會實現它。
正如開頭介紹的那樣,我們需要獲取用戶的授權,從而讀取和寫入他們的健康數據。在 ViewDidLoad()中獲取授權:
override func viewDidLoad() { super.viewDidLoad() locationManager.requestWhenInUseAuthorization(); if CLLocationManager.locationServicesEnabled(){ locationManager.delegate = self locationManager.desiredAccuracy = kCLLocationAccuracyBest } else { print("Need to Enable Location"); } // 不向用戶請求許可就無法獲取用戶的 HealthKit 數據 getHealthKitPermission() }
getHealthKitPermission() 方法會調用 manager 的 authorizeHealthKit() 方法。如果一切順利,我們可以調用 setHeight() 方法,稍後我們會介紹這個方法。
func getHealthKitPermission() { // 在 HealthKitManager.swift 文件裡尋找授權情況。 healthManager.authorizeHealthKit { (authorized, error) -> Void in if authorized { // 獲得然後設置用戶的高度 self.setHeight() } else { if error != nil { print(error) } print("Permission denied.") } } }
在 HealthKitManager.swift 文件中創建 authorizeHealthKit() 方法。除此之外,我們還需要創建 HealthKit store,將 App 連接到 HealthKit 數據。
let healthKitStore: HKHealthStore = HKHealthStore() func authorizeHealthKit(completion: ((success: Bool, error: NSError!) -> Void)!) { // 聲明我們想從 HealthKit 裡讀取的健康數據的類型 let healthDataToRead = Set(arrayLiteral: HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)!) // 聲明我們想寫入 HealthKit 的數據的類型 let healthDataToWrite = Set(arrayLiteral: HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)!) // 以防萬一 OneHourWalker 在 iPad 中打開 if !HKHealthStore.isHealthDataAvailable() { print("Can't access HealthKit.") } // 請求可以讀取和寫入數據的權限 healthKitStore.requestAuthorizationToShareTypes(healthDataToWrite, readTypes: healthDataToRead) { (success, error) -> Void in if( completion != nil ) { completion(success:success, error:error) } } }
當我們請求授權獲取用戶健康數據時,需要特別表明我們只是想讀取和寫入數據。對於這個應用來說,我們想讀取用戶的身高,從而幫助他們避免撞到樹枝。我們期望 HealthKit 提供一個 HKObject 實體,我們可以把它轉換成可讀性更高的身高值。此外,我們還需要申請寫入權限,從而把用戶步行和跑步的距離寫入 HKObject 實體。
我們會在處理完 iPad 屏幕適配之後發起權限請求。
我們在 HealthKitManager.swift 文件中創建 getHeight() 方法,從 HealthKit 中讀取用戶的高度數據。
func getHeight(sampleType: HKSampleType , completion: ((HKSample!, NSError!) -> Void)!) { // 創建斷言,以查詢高度 let distantPastHeight = NSDate.distantPast() as NSDate let currentDate = NSDate() let lastHeightPredicate = HKQuery.predicateForSamplesWithStartDate(distantPastHeight, endDate: currentDate, options: .None) // 獲得最近的高度值 let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false) // 從 HealthKit 裡獲取最近的高度值 let heightQuery = HKSampleQuery(sampleType: sampleType, predicate: lastHeightPredicate, limit: 1, sortDescriptors: [sortDescriptor]) { (sampleQuery, results, error ) -> Void in if let queryError = error { completion(nil, queryError) return } // 把第一個 HKQuantitySample 作為最近的高度值 let lastHeight = results!.first if completion != nil { completion(lastHeight, nil) } } // 是時候執行查詢了 self.healthKitStore.executeQuery(heightQuery) }
查詢身高數據的第一步是創建一個斷言,用它定義時間參數。我們會獲取一段時間內的所有身高信息。當然,這會返回一個數組。我們只想要最近的身高,所以我們對數據排序,讓數據中最新的數據排在最前面。
在創建查詢的過程中,我們把數組的長度限制為一。處理完可能出現的錯誤之後,我們把第一個也是唯一一個 item 作為 lastHeight 的結果。接著,調用 getHeight() 的回調函數。最後,執行我們的查詢操作。
回到 TimerViewController.swift,在用戶授權完成之後,用戶開始使用 App 之前,需要在 getHealthKitPermission() 中調用 setHeight()。
var height: HKQuantitySample?
首先,我們需要給 HKQuantitySample 實例聲明一個高度變量。
func setHeight() { // 創建高度 HKSample。 let heightSample = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight) // 調用 HealthKitManager 的 getSample() 方法,來獲取用戶的高度。 self.healthManager.getHeight(heightSample!, completion: { (userHeight, error) -> Void in if( error != nil ) { print("Error: \(error.localizedDescription)") return } var heightString = "" self.height = userHeight as? HKQuantitySample // 把高度轉換成用戶本地的計量單位。 if let meters = self.height?.quantity.doubleValueForUnit(HKUnit.meterUnit()) { let formatHeight = NSLengthFormatter() formatHeight.forPersonHeightUse = true heightString = formatHeight.stringFromMeters(meters) } // 設置 label 顯示用戶的高度。 dispatch_async(dispatch_get_main_queue(), { () -> Void in self.heightLabel.text = heightString }) }) }
在 share() 方法之前創建 setHeigth() 方法。我們請求的身高數據會返回一個 HKQuantity,它的 identifier 是 HKQuantityTypeIdentifierHeight。
接著,我們調用 manager 中的 getHeight() 方法。有了身高數據,我們需要將它轉換成合適的字符串,展示到我們的 Label 控件中。照例,我們要考慮所有可能的錯誤。
現在,用戶能夠打開 App,查看他們的身高,將身高記錄到健康應用中,開始計時,然後追蹤跑步或者走路的距離。下一步就是處理寫入數據,讓用戶可以記錄所有的健身數據。
用戶完成運動之後(無論是整整一小時還是不到一小時),他/她會點擊 Share 按鈕,將他們的距離發送給 Health 應用。所以我們在 share() 方法中調用 HealthKitManager.swift 裡的 saveDistance() 方法,這樣數據和日期都能被歸檔,明天用戶可以試著去挑戰他/她自己的記錄!
@IBAction func share(sender: AnyObject) { healthManager.saveDistance(distanceTraveled, date: NSDate()) }
回到 manager,我們創建 saveDistance() 方法,首先,我們需要讓 HealthKit 知道我們想寫入跑步距離和走路步數,接著,我們將計量單位換成英裡並賦值給實體。HealthKit 的 saveObject() 方法將會寫入用戶的健康數據。
func saveDistance(distanceRecorded: Double, date: NSDate ) { // 設置跑步距離或走路步數的數量的類型 let distanceType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning) // 把計量單位設置成英裡 let distanceQuantity = HKQuantity(unit: HKUnit.mileUnit(), doubleValue: distanceRecorded) // 設置正式的 Quantity Sample。 let distance = HKQuantitySample(type: distanceType!, quantity: distanceQuantity, startDate: date, endDate: date) // 保存距離數量,把健康數據寫入 HealthKit healthKitStore.saveObject(distance, withCompletion: { (success, error) -> Void in if( error != nil ) { print(error) } else { print("The distance has been recorded! Better go check!") } }) }
打開健康應用,記錄的數據會包含在 Walking + Running Distance 裡。我們可以查看具體的記錄:Health Data tab > Fitness > Walking + Running Distance > Show All Data。我們的數據就在這清單裡。點擊任意一行就會看到我們的圖標(目前還空著)。再次點擊這一行,就會出現所有的詳細信息。
有了 OneHourWalker,我們就可以為全世界 iOS 用戶的健康貢獻我們的力量。然而,這僅僅是一個開始。HealthKit 有無限可能。
當然,讓用戶查看所有追蹤信息非常有用,人們可以對比每天、每周或者任意時間的數據,從而給自己動力。但是真正有價值的是,開發者可以用無數種新的、有創造力的、有趣的方式來獲取數據。
此外,HealthKit 應用的測試會非常有趣!
這裡是我們最終版本的 OneHourWalker。
作者:AppCoda,原文鏈接
譯者:Crystal Sun;校對:numbbbbb;定稿:Cee