【編者按】本文作者為 Matthew Maher,文章手把手地介紹了如何借助 HealthKit 建立簡(jiǎn)單的健身應(yīng)用,包含諸多代碼實(shí)例。本文系國(guó)內(nèi) ITOM 管理平臺(tái) OneAPM 編譯呈現(xiàn)。
根據(jù)新聞報(bào)導(dǎo),健康與健美在今時(shí)今日的重要程度比已往任何時(shí)候都高。說起來有點(diǎn)可笑,似乎就在幾天之前,筆者就見到過類似的新聞?;蛟S,這是當(dāng)人逐漸變老之后揮之不去的感覺吧——渴望保持健康以及健美的感覺。不管怎么說,健康與健美是一個(gè)重要話題。技術(shù)的進(jìn)步,尤其是移動(dòng)應(yīng)用與硬件世界的不斷提高,正為這個(gè)似乎日益成長(zhǎng)的話題帶來全新的契機(jī)。
HealthKit 是蘋果公司推出的一款移動(dòng)應(yīng)用平臺(tái),旨在為重要、可追蹤的健康數(shù)據(jù)與注重健康、熱衷鍛煉的科技消費(fèi)者搭起橋梁。這很酷。用戶可以輕松地追蹤一段時(shí)間內(nèi)可測(cè)量的健身與健康數(shù)據(jù)。除了了解自身的健康數(shù)據(jù),看到圖表中喜人的增長(zhǎng)曲線也的確鼓舞人心。
正如人們想象的那樣,在管理健康信息時(shí)安全是非常重要的考慮因素。HealthKit 直截了當(dāng)?shù)貙⑺?HealthKit 信息的絕對(duì)控制權(quán)置于用戶的手中。用戶可以授權(quán)或拒絕任何應(yīng)用對(duì)其健康數(shù)據(jù)發(fā)出的讀取請(qǐng)求。
作為開發(fā)者,我們需要征求許可才能從/向 HealthKit 讀取/寫入數(shù)據(jù)。實(shí)際上,我們需要明確地聲明打算讀取或改變的數(shù)據(jù)。此外,任何使用 HealthKit 的應(yīng)用都必須包含隱私政策,這樣一來,用戶才能對(duì)其信息的處理感到更加放心。
關(guān)于 OneHourWalker
在本文中,我們將打造一個(gè)有趣的小應(yīng)用,它會(huì)從 HealthKit 讀取數(shù)據(jù),也會(huì)向其寫入新數(shù)據(jù)。來見一見 OneHourWalker 吧。
OneHourWalker 是一款追蹤使用者在一個(gè)小時(shí)內(nèi)行走或跑步之距離的健身應(yīng)用。用戶可以將距離與 HealthKit 分享,之后就能在健康應(yīng)用中讀取之。我知道,一個(gè)小時(shí)聽起來有點(diǎn)過于樂觀了(至少筆者本人可能無(wú)法堅(jiān)持下去)。因此,用戶也可以提早中止計(jì)數(shù),并分享距離。
額,到目前為止,似乎 OneHourWalker 只會(huì)向 HealthKit 寫入數(shù)據(jù)。我們需要讀取什么數(shù)據(jù)呢?
好問題!在步行鍛煉時(shí),我喜歡選擇鄉(xiāng)間或林間小路。常常,我會(huì)遇到樹枝低垂的區(qū)域。而我是一條身高 193cm 的漢子,這真的讓我很苦惱。解決辦法是:從 HealthKit 讀取用戶的身高數(shù)據(jù),將之打印為應(yīng)用的一個(gè)標(biāo)簽。這個(gè)標(biāo)簽可以作為對(duì)用戶的善意提醒,這樣,他們就能避免在步行時(shí)被樹枝打到。
首先,點(diǎn)此下載 OneHourWalker 的初始項(xiàng)目。先試著跑起來,找找應(yīng)用運(yùn)行的感覺。計(jì)數(shù)器與地點(diǎn)追蹤功能已經(jīng)在運(yùn)行了,所以我們只需專注于 HealthKit 實(shí)現(xiàn)。注意,當(dāng)?shù)竭_(dá) 60 分鐘時(shí)間點(diǎn)時(shí),計(jì)算器與追蹤都會(huì)停止。
啟用 HealthKit
首先,在我們的應(yīng)用中啟用 HealthKit。在項(xiàng)目導(dǎo)航中,點(diǎn)擊 OneHourWalker,之后點(diǎn)擊 Targets 下面的 OneHourWalker,之后選擇屏幕頂部的 Capabilities 選項(xiàng)。
查看 Capabilities 列表的底部,啟用 HealthKit
。這一簡(jiǎn)單的操作會(huì)將 HealthKit 權(quán)限添加到 App ID,將 HealthKit 鍵添加到 info plist 文件,將 HealthKit 權(quán)限添加到授權(quán)文件,并且與 HealthKit.framework
相連接。就是這么簡(jiǎn)單。
開始編程
接下來,跳轉(zhuǎn)到 TimerViewController.swift
,開始將 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()
所有 HealthKit 工作都會(huì)在 HealthKitManager.swift
中進(jìn)行。它會(huì)包含重要的方法,我們很快就會(huì)談到。
正如在前文介紹部分所述,我們需要取得用戶的許可,才能讀取并修改他們的健康數(shù)據(jù)。在 viewDidLoad()
中,我們就得這么做。
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()
方法。不過,我們很快會(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 中,我們會(huì)創(chuàng)建 authorizeHealthKit() 方法。然而,除此之外,我們需要?jiǎng)?chuàng)建 HealthKit 存儲(chǔ),用于連接應(yīng)用與 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)
}
}
}
在請(qǐng)求獲取用戶健康數(shù)據(jù)的授權(quán)時(shí),我們需要明確指定打算讀取以及修改的信息。對(duì)本例而言,我們需要讀取用戶的身高,從而幫助他們躲避有危險(xiǎn)的低垂枝丫。我們希望 HealthKit 能提供一個(gè)可以轉(zhuǎn)化為可理解的身高的 HKObject 量。此外,我們還要獲得修改 HKObject 量的許可,以記錄用戶的行走及跑步距離。
在處理好 OneHourWalker 與 iPad 通信的可能性后,我們做出官方請(qǐng)求。
在 HealthKitManager.swift
中,創(chuàng)建從 HealthKit 讀取用戶身高數(shù)據(jù)的 getHeight()
方法。
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)
}
查詢身高數(shù)據(jù)的第一步是創(chuàng)建一個(gè)斷言以定義時(shí)間參數(shù)。我們是在請(qǐng)求一段時(shí)間內(nèi)的所有身高數(shù)據(jù)——與當(dāng)前日期相距甚遠(yuǎn)的一個(gè)過去的日期。顯然,這會(huì)返回一個(gè)數(shù)組。然而,我們只想要最近期的身高,因此,我們請(qǐng)求數(shù)據(jù)時(shí)可以讓最新的數(shù)據(jù)排在數(shù)組的最前頭。
在構(gòu)建這一查詢時(shí),我們會(huì)把數(shù)組的長(zhǎng)度限制為1。在考慮好出現(xiàn)錯(cuò)誤的可能性后,我們會(huì)將結(jié)果中的首個(gè)也即唯一一個(gè)數(shù)組項(xiàng)目分配給 lastHeight。接下來,完善 getHeight() 方法。最后,針對(duì)用戶的健康數(shù)據(jù)執(zhí)行查詢。
回到 TimerViewController.swift
,在 app 真正投入使用之前,假設(shè)用戶授權(quán)了適當(dāng)?shù)脑S可,則 setHeight()
方法會(huì)被 getHealthKitPermission()
調(diào)用。
var height: HKQuantitySample?
首先,我們需要為 HKQuantitySample 實(shí)例聲明一個(gè)身高變量。
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)建 setHeight()
方法。我們請(qǐng)求的身高數(shù)據(jù)樣本以 HKQuantity
返回,標(biāo)識(shí)符 HKQuantityTypeIdentifierHeight
知道這一對(duì)象。
接下來,調(diào)用在 manager 中創(chuàng)建的 getHeight()
方法。有了身高樣本,我們還需要將之翻譯為恰當(dāng)?shù)淖址哉故驹跇?biāo)簽中。與往常一樣,考慮所有可能的錯(cuò)誤情況是很重要的。
到此,用戶就可以打開 app,查看他們的身高(如果他的健康應(yīng)用中記錄著身高數(shù)據(jù)),開啟計(jì)時(shí)器,追蹤他跑步或行走的距離了。接下來,我們要處理將距離數(shù)據(jù)寫入健康應(yīng)用的過程,這樣,用戶才能在同一個(gè)應(yīng)用中保存其所有的健身數(shù)據(jù)。
在用戶結(jié)束外出鍛煉之后,不管有沒有到60分鐘,他可能會(huì)使用 Share(分享)按鈕將其辛苦賺得的運(yùn)動(dòng)距離發(fā)送到健康應(yīng)用。所以,在 share() 方法中,我們需要調(diào)用 HealthKitManager.swift
的 saveDistance()
方法來實(shí)現(xiàn)這一過程。在這個(gè)方法中,我們會(huì)發(fā)送運(yùn)動(dòng)距離以及取得該距離的日期。這樣,用戶便能在第二天爭(zhēng)取更好的成績(jī)。
@IBAction func share(sender: AnyObject) {
healthManager.saveDistance(distanceTraveled, date: NSDate())
}
接下來,回到 manager,我們要在此處創(chuàng)建 saveDistance()
方法。首先,我們要讓 HealthKit 知道我們打算寫入一個(gè)代表步行及跑步距離的量。之后,將度量單位設(shè)置為英里,并賦值官方的樣本量。HealthKit 的 saveObject()
方法會(huì)將此數(shù)據(jù)寫入用戶的健康數(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!")
}
})
}
跳轉(zhuǎn)到健康應(yīng)用,所記錄的數(shù)據(jù)會(huì)出現(xiàn)在 Walking + Running Distance(行走+跑步距離)一行(如果已經(jīng)啟用)。此外,依照下面的路徑,我們可以看到詳細(xì)的樣本數(shù)據(jù):Health Data tab(健康數(shù)據(jù)選項(xiàng)卡) > Fitness(健身) > Walking + Running Distance(行走+跑步距離) > Show All Data(顯示所有數(shù)據(jù))。我們的數(shù)據(jù)就在此列表中。輕擊一個(gè)單元,我們的圖標(biāo)(目前還未設(shè)置)就會(huì)與距離一同出現(xiàn)。再次點(diǎn)擊此單元,就能看到完整的細(xì)節(jié)數(shù)據(jù)。
借助 OneHourWalker,我們便能為全世界 iOS 用戶的身體健康貢獻(xiàn)一份力量。然而,這只是一個(gè)開始。在使用 HealthKit 讀取并修改健康數(shù)據(jù)的道路上,還有非常多的可能性。
當(dāng)然,對(duì)用戶而言,擁有這些可追蹤數(shù)據(jù)的好處很多。人們可以輕松地按照日期、星期進(jìn)行比較,從而激勵(lì)自己朝著目標(biāo)努力。不過,真正的偉大之處在于,開發(fā)者可以提供全新的,富有創(chuàng)造力的有趣方法來獲取數(shù)據(jù)。
歡迎大家對(duì) HealthKit 應(yīng)用進(jìn)行測(cè)試。點(diǎn)擊此處查看 OneHourWalker 的最終版本。
本文系 OneAPM 工程師編譯整理。OneAPM Mobile Insight 以真實(shí)用戶體驗(yàn)為度量標(biāo)準(zhǔn)進(jìn)行 Crash 分析,監(jiān)控網(wǎng)絡(luò)請(qǐng)求及網(wǎng)絡(luò)錯(cuò)誤,提升用戶留存。訪問 OneAPM 官方網(wǎng)站感受更多應(yīng)用性能優(yōu)化體驗(yàn),想閱讀更多技術(shù)文章,請(qǐng)?jiān)L問 OneAPM 官方技術(shù)博客。
本文轉(zhuǎn)自 OneAPM 官方博客