原文鏈接
作者:AppCoda
原文日期:2016-03-22
看新聞我們也知道,比起歷史上任何一個(gè)時(shí)刻,健身和健康在今天越來(lái)越重要了。說(shuō)起了也挺好笑的,我似乎記得幾天前新聞告訴我同樣的事情,也許是因?yàn)槟昙o(jì)越來(lái)越大的緣故,更需要健康和健身了。不管怎么說(shuō),這是一個(gè)熱門(mén)話題。隨著技術(shù)的不斷進(jìn)步,手機(jī)應(yīng)用和硬件在世界范圍內(nèi)都變得流行起來(lái),這些都把日益流行的健身健康話題加入了新的元素。
HealthKit 是蘋(píng)果公司的重要橋梁,把重要的跟蹤的健康數(shù)據(jù)同有健康意識(shí)的科技消費(fèi)者、運(yùn)動(dòng)迷、平常使用 iPhone 的人連接了起來(lái)。這很酷,用戶(hù)可以很容易的就追蹤衡量一段時(shí)間內(nèi)的健身和健康數(shù)據(jù),除了意識(shí)到的好處之外,我們看到圖標(biāo)中的向上走的曲線,就能給我們極大的鼓勵(lì),激勵(lì)我們繼續(xù)運(yùn)動(dòng)。
正如我們能想象到的,在管理健康信息時(shí),數(shù)據(jù)安全成為非常重要的因素。HealthKit 對(duì)于所有的 HealthKit 信息有絕對(duì)的控制權(quán),會(huì)直接傳遞到用戶(hù)手中。用戶(hù)可以準(zhǔn)許或者拒絕任何 App 獲取他們的健康數(shù)據(jù)的請(qǐng)求。
對(duì)于開(kāi)發(fā)者來(lái)說(shuō),我們需要請(qǐng)求許可方能讀取或者寫(xiě)入 HealthKit 數(shù)據(jù)。實(shí)際上,我們需要特別聲明一下,我們想影響獲取具體哪些數(shù)據(jù)。另外,任何使用 HealthKit 的 App 必須要包含一份 Privacy Policy(隱私協(xié)議),這樣用戶(hù)在進(jìn)行信息交易時(shí)會(huì)覺(jué)得更舒服一些。
關(guān)于 OneHourWalker 走路一小時(shí)
今天,我們要?jiǎng)?chuàng)建一個(gè)非常有趣的 App,既能讀取 HealthKit 中的信息,也能寫(xiě)入新的數(shù)據(jù)??匆幌?OneHourWalker 的外表吧:

OneHourWalker 是健身 App,能夠跟蹤用戶(hù)在一個(gè)小時(shí)內(nèi)走路或跑步的距離。用戶(hù)可以分享距離到 HealthKit上,就會(huì)被其他的健康 App 收集到數(shù)據(jù)。我知道,一小時(shí)聽(tīng)起來(lái)不太積極,至少對(duì)我而言是這樣。最后,用戶(hù)可以提早結(jié)束健身,仍然可以分享距離。
所以,聽(tīng)起來(lái)只需要把數(shù)據(jù)寫(xiě)入 HealthKit 即可。不過(guò)我們要讀取的數(shù)據(jù)是什么?
好問(wèn)題!當(dāng)我走路時(shí),我有可能是在羊腸小道上步行,也可能在森林中漫步。我常常穿越一些 low-hanging branches 區(qū)域。Being as I am 6’4”,這會(huì)帶來(lái)一些問(wèn)題。我們的解決方案是:我們會(huì)從 HealthKit 中讀取用戶(hù)的高度,然后顯示到 Label 控件上。這樣會(huì)比較友好地提示用戶(hù)的限制,這樣他或她就能避免在走路時(shí)糾纏在一起。
為了方便開(kāi)始,這里有一個(gè) OneHourWalker 的初始工程,下載然后運(yùn)行,看起來(lái)好像 App 可以運(yùn)行。計(jì)時(shí)器和定位系統(tǒng)都已經(jīng)在運(yùn)行了,所以我們只需要將注意力放在實(shí)施 HealthKit上,注意一下,六十分鐘后,計(jì)時(shí)器和定位系統(tǒng)就會(huì)自動(dòng)停止。
開(kāi)啟 HealthKit
第一步就是在應(yīng)用中開(kāi)啟 HealthKit 功能,在 Project Navigator 中,選中 OneHourWalker,然后點(diǎn)擊 Targets 下方的 OneHourWalker。接著,在屏幕上方的 tab 欄中點(diǎn)擊 Capabilities。

找到 Capabilities 清單底下,把 HealthKit 調(diào)到 On 狀態(tài)。這個(gè)動(dòng)作表示:把 HealthKit 資格添加到 App ID 中,把 HealthKit key 添加到 info plist 文件里,把 HealthKit 資格添加到資格文件中,以及連接 HealthKit.framework。就是這么簡(jiǎn)單。
開(kāi)始寫(xiě)代碼吧
找到 TimerViewController.swift,開(kāi)始把 HealthKit 引入 OneHourWalker。首先,讓我們創(chuàng)建一個(gè) HealthKitManager 實(shí)例。
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 里包含了所有有關(guān) HealthKit 的操作。里面有一些重要的方法,我們馬上就要開(kāi)始在這個(gè)文件里進(jìn)行編程了。
正如我們?cè)陂_(kāi)頭介紹的那樣,我們需要獲取用戶(hù)的許可來(lái)讀取和寫(xiě)入他們的健康數(shù)據(jù)。在 ViewDidLoad(),開(kāi)始獲取許可吧:
override func viewDidLoad() {
super.viewDidLoad()
locationManager.requestWhenInUseAuthorization();
if CLLocationManager.locationServicesEnabled(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
else {
print("Need to Enable Location");
}
// We cannot access the user's HealthKit data without specific permission.
getHealthKitPermission()
}
getHealthKitPermission() 方法會(huì)調(diào)用 manager 的 authorizeHealthKit() 方法。如果一切順利,我們可以調(diào)用 setHeight() 方法,不過(guò)很快我們就會(huì)需要更多方法了。
func getHealthKitPermission() {
// Seek authorization in HealthKitManager.swift.
healthManager.authorizeHealthKit { (authorized, error) -> Void in
if authorized {
// Get and set the user's height.
self.setHeight()
} else {
if error != nil {
print(error)
}
print("Permission denied.")
}
}
}
在 HealthKitManager.swift 文件里,我們創(chuàng)建 authorizeHealthKit() 方法。除此之外,我們還需要?jiǎng)?chuàng)建 HealthKit store,將 App 連接到 HealthKit 數(shù)據(jù)。
let healthKitStore: HKHealthStore = HKHealthStore()
func authorizeHealthKit(completion: ((success: Bool, error: NSError!) -> Void)!) {
// State the health data type(s) we want to read from HealthKit.
let healthDataToRead = Set(arrayLiteral: HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)!)
// State the health data type(s) we want to write from HealthKit.
let healthDataToWrite = Set(arrayLiteral: HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)!)
// Just in case OneHourWalker makes its way to an iPad...
if !HKHealthStore.isHealthDataAvailable() {
print("Can't access HealthKit.")
}
// Request authorization to read and/or write the specific data.
healthKitStore.requestAuthorizationToShareTypes(healthDataToWrite, readTypes: healthDataToRead) { (success, error) -> Void in
if( completion != nil ) {
completion(success:success, error:error)
}
}
}
當(dāng)我們請(qǐng)求授權(quán)獲取用戶(hù)健康數(shù)據(jù)時(shí),我們需要特別表明我們只是想讀取和寫(xiě)入數(shù)據(jù)。對(duì)于我們來(lái)說(shuō),我們想讀取用戶(hù)的高度,所以他們可以避免容易實(shí)現(xiàn)誤導(dǎo)動(dòng)作。希望 HealthKit 能夠提供一個(gè) HKObject 參數(shù),可以獲取一個(gè)可理解的高度數(shù)據(jù)。我們也需要請(qǐng)求許可方能寫(xiě)入 HKObject 參數(shù),獲取用戶(hù)的走路或者跑步距離。
在處理了 OneHourWalker 的所有可能性后,我們?cè)谝粋€(gè) iPad 真機(jī)上測(cè)試了一下,制作正式的的請(qǐng)求。
在 HealthKitManager.swift 文件中創(chuàng)建 getHeight() 方法,能夠讀取用戶(hù)的高度數(shù)據(jù)。
func getHeight(sampleType: HKSampleType , completion: ((HKSample!, NSError!) -> Void)!) {
// Predicate for the height query
let distantPastHeight = NSDate.distantPast() as NSDate
let currentDate = NSDate()
let lastHeightPredicate = HKQuery.predicateForSamplesWithStartDate(distantPastHeight, endDate: currentDate, options: .None)
// Get the single most recent height
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
// Query HealthKit for the last Height entry.
let heightQuery = HKSampleQuery(sampleType: sampleType, predicate: lastHeightPredicate, limit: 1, sortDescriptors: [sortDescriptor]) { (sampleQuery, results, error ) -> Void in
if let queryError = error {
completion(nil, queryError)
return
}
// Set the first HKQuantitySample in results as the most recent height.
let lastHeight = results!.first
if completion != nil {
completion(lastHeight, nil)
}
}
// Time to execute the query.
self.healthKitStore.executeQuery(heightQuery)
}
我們第一步就是查詢(xún)高度數(shù)據(jù)來(lái)創(chuàng)建一個(gè)猜測(cè)的時(shí)間參數(shù),我們獲取一段時(shí)間內(nèi)的所有高度信息,從過(guò)去到現(xiàn)在,當(dāng)然了,這就會(huì)返回給我們一個(gè)數(shù)組。我們只想要最近的高度,所以我們會(huì)讓數(shù)據(jù)中最新的數(shù)據(jù)排在最前面。
在創(chuàng)建查詢(xún)的過(guò)程中,我們會(huì)限制數(shù)組的總數(shù)為一。把可能出錯(cuò)的情況計(jì)算在內(nèi),我們把第一個(gè)也是唯一一個(gè) item 作為 lastHeight 的結(jié)果。接著,我們搞定 getHeight() 方法。最后,執(zhí)行查詢(xún)對(duì)應(yīng)用戶(hù)的健康數(shù)據(jù)。
回到 TimerViewController.swift,在 App 出現(xiàn)在用戶(hù)之前,我們假設(shè)已經(jīng)獲取了用戶(hù)的準(zhǔn)許,setHeight() 在 getHealthKitPermission() 被調(diào)用。
var height: HKQuantitySample?
首先,我們需要聲明一個(gè)高度變量作為 HKQuantitySample 的實(shí)例。
func setHeight() {
// Create the HKSample for Height.
let heightSample = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)
// Call HealthKitManager's getSample() method to get the user's height.
self.healthManager.getHeight(heightSample!, completion: { (userHeight, error) -> Void in
if( error != nil ) {
print("Error: \(error.localizedDescription)")
return
}
var heightString = ""
self.height = userHeight as? HKQuantitySample
// The height is formatted to the user's locale.
if let meters = self.height?.quantity.doubleValueForUnit(HKUnit.meterUnit()) {
let formatHeight = NSLengthFormatter()
formatHeight.forPersonHeightUse = true
heightString = formatHeight.stringFromMeters(meters)
}
// Set the label to reflect the user's height.
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.heightLabel.text = heightString
})
})
}
上面的 share() 方法,我們會(huì)創(chuàng)建我們的 setHeigth() 方法。高度數(shù)據(jù)例子表明我們請(qǐng)求之后返回的一個(gè) HKQuantity,它的 identifier 也就是 HKQuantityTypeIdentifierHeight。
下一步,我們調(diào)用 getHeight() 方法,也就是我們?cè)?manager 中創(chuàng)建的。有了高度,我們需要將它轉(zhuǎn)換成合適的字符串,展示到我們的 Label 控件中。照例,我們要考慮所有可能的錯(cuò)誤。
就這點(diǎn)而言,用戶(hù)能夠打開(kāi) App,看一下他們的高度,假設(shè)這里有記錄高度的 App,開(kāi)始計(jì)時(shí)器,然后追蹤跑步或者走路的距離。下一步就是處理寫(xiě)入數(shù)據(jù),所以用戶(hù)可以記錄所有的健身數(shù)據(jù)。
60分鐘后或更短時(shí)間內(nèi)用戶(hù)完成運(yùn)動(dòng),他/她會(huì)點(diǎn)擊 Share 按鈕將他們的距離發(fā)送到 Health 應(yīng)用里。所以,在 share() 方法中,讓我們調(diào)用 HealthKitManager.swift 里的 saveDistance() 方法,這樣,數(shù)據(jù)和日期都能被歸檔,用戶(hù)可以明天試著去挑戰(zhàn)他/她自己的記錄!
@IBAction func share(sender: AnyObject) {
healthManager.saveDistance(distanceTraveled, date: NSDate())
}
回到 manager,我們創(chuàng)建 saveDistance() 方法,首先,我們需要讓 HealthKit 知道我們想寫(xiě)入跑步距離和走路步數(shù),接著,我們將計(jì)量單位換成英里,分配正式的數(shù)量。HealthKit 的 saveObject() 方法將會(huì)寫(xiě)入用戶(hù)的健康數(shù)據(jù)。
func saveDistance(distanceRecorded: Double, date: NSDate ) {
// Set the quantity type to the running/walking distance.
let distanceType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
// Set the unit of measurement to miles.
let distanceQuantity = HKQuantity(unit: HKUnit.mileUnit(), doubleValue: distanceRecorded)
// Set the official Quantity Sample.
let distance = HKQuantitySample(type: distanceType!, quantity: distanceQuantity, startDate: date, endDate: date)
// Save the distance quantity sample to the HealthKit Store.
healthKitStore.saveObject(distance, withCompletion: { (success, error) -> Void in
if( error != nil ) {
print(error)
} else {
print("The distance has been recorded! Better go check!")
}
})
}
回到 Health 應(yīng)用里,記錄的數(shù)據(jù)會(huì)包含在 Walking + Running Distance 里。當(dāng)然,我們也能看到一個(gè)具體的例子:Health Data tab > Fitness > Walking + Running Distance > Show All Data。我們的數(shù)據(jù)就在這清單里。點(diǎn)擊一行,然后就會(huì)看到我們的圖標(biāo)(目前還空著)。再次點(diǎn)擊這一行,就會(huì)出現(xiàn)所有的詳細(xì)信息。

有了 OneHourWalker,我們已經(jīng)成功地為全世界的 iOS 用戶(hù)的健康貢獻(xiàn)了我們的力量。然而,這僅僅是一個(gè)開(kāi)始。仍然有更多利用 HealthKit 讀取和寫(xiě)入健康數(shù)據(jù)的事情需要我們來(lái)做。
當(dāng)然,能夠獲取用戶(hù)的所有跟蹤信息也是非常棒的,人們可以非常容易的進(jìn)行每天和每天的比較,周和周的對(duì)比,以及其他朝著目標(biāo)推進(jìn)的對(duì)比。真正偉大之處在于,開(kāi)發(fā)者能夠提供全新的、有創(chuàng)造力的、有趣的方式來(lái)捕獲這些數(shù)據(jù)。
同樣的,HealthKit 應(yīng)用是最有趣的測(cè)試了!
這里是我們最終版本的 OneHourWalker。
本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán)。