作者:ANUSHK MITTAL,原文鏈接,原文日期:2016-01-18
譯者:Wiilen
如今,對睡眠監(jiān)測的創(chuàng)新已經(jīng)成為一種潮流。用戶比以前更加好奇,他們不僅想知道自己的睡眠時(shí)間,更想對一段時(shí)間內(nèi)收集的數(shù)據(jù)進(jìn)行分析,來了解自己的睡眠趨勢。科技的發(fā)展也帶動了硬件的發(fā)展,其中特別是手機(jī)的發(fā)展,使得睡眠分析這一進(jìn)步緩慢的領(lǐng)域有了全面的發(fā)展。
蘋果公司提供了一種酷炫的方式,可以安全地與用戶的個(gè)人健康信息進(jìn)行交互,并將這些信息安全地存入內(nèi)置的 Health App。你不僅可以使用 HealthKit 框架來開發(fā)一個(gè)健康類 App,也可以通過它來訪問睡眠分析數(shù)據(jù)。
在這個(gè)教程中,我將為你快速介紹 HealthKit 框架,并演示如何開發(fā)一個(gè)用于睡眠分析的簡單 App。
介紹
HealthKit 框架提供了一種結(jié)構(gòu),用于將數(shù)據(jù)存入名為 HealthKit store 的加密的數(shù)據(jù)庫。你可以使用HKHealthStore類來訪這個(gè)數(shù)據(jù)庫。iPhone 與 Apple Watch 都有自己的 HealthKit store。健康數(shù)據(jù)能夠在 Apple Watch 與 iPhone 中進(jìn)行同步,而舊數(shù)據(jù)會周期性地從 Apple Watch 中清除以節(jié)約空間。HealthKit 與 Health App 在 iPad 上無法使用。
HealthKit 是一個(gè)強(qiáng)大的工具,可以幫助你開發(fā)基于健康數(shù)據(jù)的 iOS 或 watchOS App。它專門用于管理各種來源的數(shù)據(jù),根據(jù)用戶偏好,它能自動合并不同來源的數(shù)據(jù)。Apps 也可以訪問不同來源的原始數(shù)據(jù),自己合并這些數(shù)據(jù)。這些數(shù)據(jù)不僅可用于身體測量、健康數(shù)據(jù)或營養(yǎng)攝入監(jiān)測,也可以用于睡眠分析。
在這篇文章之后的部分,我將演示在 iOS 上如何使用 HealthKit 框架存儲和訪問睡眠分析數(shù)據(jù)。這些方法也可用在 watchOS App 上。請注意這篇教程中使用的是 Swift 2.0 與 Xcode 7。為了完成這篇教程,你需要確保你使用的是 Xcode 7 或以上版本。
在開始之前,下載初始工程并解壓它。我已經(jīng)為你創(chuàng)建了具有基本功能的 UI。運(yùn)行這個(gè)工程,你可以看到一個(gè)計(jì)時(shí)器 UI,在你點(diǎn)擊開始按鈕之后,它會開始計(jì)時(shí)。
使用 HealthKit 框架
這個(gè) Apps 的目的是保存睡眠分析數(shù)據(jù),并使用start與Stop按鈕來獲取這些數(shù)據(jù)。要使用 HealthKit,你首先需要允許在 app bundle 中使用 HealthKit。在你的項(xiàng)目中,導(dǎo)航到 target -> capabilities 并開啟 HealthKit。

下一步,你需要使用以下代碼在ViewController中創(chuàng)建一個(gè)HKHealthStore的實(shí)例:
let HealthStore = HKHealthStore()
之后我們會使用HKHealthStore實(shí)例來訪問 HealthKit store。
前面提到過,HealthKit 允許用戶控制他們的健康數(shù)據(jù)。所以你首先需要獲得用戶授權(quán),才能訪問(讀/寫)用戶睡眠分析數(shù)據(jù)。要做到這一點(diǎn),先 import 內(nèi)置的HealthKit框架,并更新ViewDidLoad方法:
override func viewDidLoad() {
super.viewDidLoad()
let typestoRead = Set([
HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis)!
])
let typestoShare = Set([
HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis)!
])
self.healthStore.requestAuthorizationToShareTypes(typestoShare, readTypes: typestoRead) { (success, error) -> Void in
if success == false {
NSLog(" Display not allowed")
}
}
}
這部分代碼會提示用戶允許或拒絕授權(quán)。在 completion block 中,你可以處理成功或錯誤信息,并獲取最終結(jié)果。用戶不需要給予你的 App 所請求的全部權(quán)限,你需要在 App 中優(yōu)雅地處理錯誤。
但為了方便測試,你需要選擇“允許”來授權(quán)你的 App 訪問設(shè)備上的健康數(shù)據(jù)。

寫入睡眠分析數(shù)據(jù)
首先,我們?nèi)绾潍@取睡眠分析數(shù)據(jù)?根據(jù)蘋果公司的文檔,每個(gè)睡眠分析樣本數(shù)據(jù)只含有一個(gè)值。HealthKit 使用兩個(gè)或以上在時(shí)間上重疊的樣本數(shù)據(jù),來表示用戶躺在床上并處于睡眠中。通過對比這些樣本數(shù)據(jù)的開始及結(jié)束時(shí)間,App 可以計(jì)算出一些二級統(tǒng)計(jì)數(shù)據(jù):
- 用戶入睡所需要的時(shí)間
- 用戶躺在床上的時(shí)間里,實(shí)際睡著的時(shí)間所占的百分比
- 用戶仍躺在床上時(shí),醒過來的次數(shù)
- 用戶在床上度過的時(shí)間和睡眠時(shí)間的總和

簡單來說,你需要使用以下方法將睡眠分析數(shù)據(jù)存入 HealthKit Store:
- 我們需要定義兩個(gè)
NSDate對象,對應(yīng)到開始時(shí)間與結(jié)束時(shí)間。 - 之后使用
HKCategoryTypeIdentifierSleepAnalysis來創(chuàng)建一個(gè)HKObjectType實(shí)例。 - 我們需要創(chuàng)建一個(gè)類型為
HKCategorySample的新對象。你通常使用類別樣本(Category Sample)來記錄睡眠數(shù)據(jù)。獨(dú)立樣本(Individual Sample)用來表示在某些時(shí)間段中,用戶躺在床上或處于睡眠中。所以我們需要創(chuàng)建一個(gè)記錄用戶躺在床上時(shí)間(In bed)的樣本與一個(gè)記錄用戶處于睡眠中(Asleep)的樣本,這兩個(gè)樣本可能會有部分時(shí)間重疊。 - 最后,我們使用
HKHealthStore類中的saveObject方法來保存對象。
編輯提示:關(guān)于樣本的類型,你可以在HealthKit Constants Reference中找到更多詳細(xì)信息。
如果你想要將上述文字翻譯成 Swift 代碼,下面是部分代碼,用于保存睡眠分析數(shù)據(jù),包括在床上度過的時(shí)間數(shù)據(jù)和睡眠時(shí)間的數(shù)據(jù)。請將下列方法插入到ViewController中:
func saveSleepAnalysis() {
// alarmTime 和 endTime 都是 NSDate 對象
if let sleepType = HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis) {
// 我們創(chuàng)建一個(gè)新的對象,并將它導(dǎo)入到 Health app 中
let object = HKCategorySample(type:sleepType, value: HKCategoryValueSleepAnalysis.InBed.rawValue, startDate: self.alarmTime, endDate: self.endTime)
// 最后,我們保存這個(gè)對象
healthStore.saveObject(object, withCompletion: { (success, error) -> Void in
if error != nil {
// 錯誤處理
return
}
if success {
print("My new data was saved in HealthKit")
} else {
// 另一個(gè)錯誤處理
}
})
let object2 = HKCategorySample(type:sleepType, value: HKCategoryValueSleepAnalysis.Asleep.rawValue, startDate: self.alarmTime, endDate: self.endTime)
healthStore.saveObject(object2, withCompletion: { (success, error) -> Void in
if error != nil {
// 錯誤處理
return
}
if success {
print("My new data (2) was saved in HealthKit")
} else {
// 另一個(gè)錯誤處理
}
})
}
}
我們想要將睡眠分析數(shù)據(jù)存入 HealthKit 時(shí),可以調(diào)用這個(gè)函數(shù)。
讀取睡眠分析數(shù)據(jù)
為了讀取睡眠分析數(shù)據(jù),我們將創(chuàng)建一個(gè)請求。你需要從使用HKCategoryTypeIdentifierSleepAnalysis來創(chuàng)建一個(gè)HKObjectType類的實(shí)例開始。你可能想使用 predicate,通過startDate和endDate這些詞來過濾獲取的數(shù)據(jù),這是一些對應(yīng)到某些時(shí)間段的NSDate對象。你也需要創(chuàng)建一個(gè)sortDescriptor來對獲取數(shù)據(jù)的請求進(jìn)行排序,以選出你需要的結(jié)果。
用于獲取睡眠分析數(shù)據(jù)的代碼如下:
func retrieveSleepAnalysis() {
// 首先,我們定義需要的對象類型
if let sleepType = HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis) {
// 使用 sortDescriptor 來獲取時(shí)間由近到遠(yuǎn)的數(shù)據(jù)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
// 創(chuàng)建查詢請求,以及一個(gè) completion block
let query = HKSampleQuery(sampleType: sleepType, predicate: nil, limit: 30, sortDescriptors: [sortDescriptor]) { (query, tmpResult, error) -> Void in
if error != nil {
// 錯誤處理
return
}
if let result = tmpResult {
// 處理取得的數(shù)據(jù)
for item in result {
if let sample = item as? HKCategorySample {
let value = (sample.value == HKCategoryValueSleepAnalysis.InBed.rawValue) ? "InBed" : "Asleep"
print("Healthkit sleep: \(sample.startDate) \(sample.endDate) - value: \(value)")
}
}
}
}
// 最后,執(zhí)行查詢請求
healthStore.executeQuery(query)
}
}
上面的代碼向 HealthKit 發(fā)出查詢請求,來獲得所有的睡眠分析數(shù)據(jù),再對這些數(shù)據(jù)以時(shí)間降序進(jìn)行排序。之后每條請求打印出了開始時(shí)間與結(jié)束時(shí)間,并標(biāo)明是在床上度過的類型數(shù)據(jù),或是在睡眠狀態(tài)的類型數(shù)據(jù)。我將請求的時(shí)間設(shè)為30,來獲取過去30秒內(nèi)記錄的樣本。你也可以使用 predicate 方法來自定義開始與結(jié)束時(shí)間。
App 測試
在這個(gè) demo 中,我使用一個(gè)NSTimer對象,在你點(diǎn)擊了開始按鈕之后進(jìn)行計(jì)時(shí)。在開始的時(shí)候與結(jié)束的時(shí)候,我們各創(chuàng)建了一個(gè)NSDate對象,將睡眠分析數(shù)據(jù)保存為經(jīng)過的時(shí)間。在調(diào)用stop方法時(shí),你可以調(diào)用saveSleepAnalysis()和retrieveSleepAnalysis()來保存并獲取睡眠數(shù)據(jù)。
@IBAction func stop(sender: AnyObject) {
endTime = NSDate()
saveSleepAnalysis()
retrieveSleepAnalysis()
timer.invalidate()
}
在你的 App 中,你可能想要改變NSDate對象,為躺在床上和處于睡眠中這兩種狀態(tài)選擇(可能不同的)開始與結(jié)束時(shí)間。
在你做出這些改變之后,你可以運(yùn)行這個(gè) demo 并啟動計(jì)時(shí)器。讓它運(yùn)行一段時(shí)間,然后點(diǎn)擊停止按鈕。做完這些之后,打開 Health App,你會發(fā)現(xiàn) App 中出現(xiàn)了睡眠數(shù)據(jù)。

一些關(guān)于 HealthKit App 的建議
HealthKit 提供了一個(gè)通用的平臺,通過這個(gè)平臺,App 開發(fā)者們可以方便地分享和訪問用戶數(shù)據(jù),避免獲得重復(fù)或不連續(xù)的數(shù)據(jù)。蘋果公司的審核指南中明確指出,對于那些使用了 HealthKit 并請求讀寫權(quán)限的 App,如果沒有明確演示它們的用途,可能會在審核時(shí)被拒絕。
那些保存了虛假或錯誤數(shù)據(jù)到 Health App 中的 App,可能也會被拒。這意味著你不能使用自己的算法來計(jì)算不同的健康數(shù)據(jù),比如這個(gè)教程中的睡眠分析數(shù)據(jù)。你應(yīng)該嘗試讀取內(nèi)建的傳感器數(shù)據(jù),并調(diào)整任意參數(shù)以避免計(jì)算出錯誤的數(shù)據(jù)。
你可以點(diǎn)擊這里獲取完整的 Xcode 項(xiàng)目。