[ 翻譯 ] 使用 Swift 和 HealthKit 進行睡眠分析

作者:ANUSHK MITTAL,原文鏈接,原文日期:2016-01-18
譯者:Wiilen

如今,對睡眠監(jiān)測的創(chuàng)新已經(jīng)成為一種潮流。用戶比以前更加好奇,他們不僅想知道自己的睡眠時間,更想對一段時間內(nèi)收集的數(shù)據(jù)進行分析,來了解自己的睡眠趨勢。科技的發(fā)展也帶動了硬件的發(fā)展,其中特別是手機的發(fā)展,使得睡眠分析這一進步緩慢的領(lǐng)域有了全面的發(fā)展。

蘋果公司提供了一種酷炫的方式,可以安全地與用戶的個人健康信息進行交互,并將這些信息安全地存入內(nèi)置的 Health App。你不僅可以使用 HealthKit 框架來開發(fā)一個健康類 App,也可以通過它來訪問睡眠分析數(shù)據(jù)。

在這個教程中,我將為你快速介紹 HealthKit 框架,并演示如何開發(fā)一個用于睡眠分析的簡單 App。

介紹

HealthKit 框架提供了一種結(jié)構(gòu),用于將數(shù)據(jù)存入名為 HealthKit store 的加密的數(shù)據(jù)庫。你可以使用HKHealthStore類來訪這個數(shù)據(jù)庫。iPhone 與 Apple Watch 都有自己的 HealthKit store。健康數(shù)據(jù)能夠在 Apple Watch 與 iPhone 中進行同步,而舊數(shù)據(jù)會周期性地從 Apple Watch 中清除以節(jié)約空間。HealthKit 與 Health App 在 iPad 上無法使用。

HealthKit 是一個強大的工具,可以幫助你開發(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。運行這個工程,你可以看到一個計時器 UI,在你點擊開始按鈕之后,它會開始計時。

使用 HealthKit 框架

這個 Apps 的目的是保存睡眠分析數(shù)據(jù),并使用startStop按鈕來獲取這些數(shù)據(jù)。要使用 HealthKit,你首先需要允許在 app bundle 中使用 HealthKit。在你的項目中,導(dǎo)航到 target -> capabilities 并開啟 HealthKit。

下一步,你需要使用以下代碼在ViewController中創(chuàng)建一個HKHealthStore的實例:

let HealthStore = HKHealthStore()

之后我們會使用HKHealthStore實例來訪問 HealthKit store。

前面提到過,HealthKit 允許用戶控制他們的健康數(shù)據(jù)。所以你首先需要獲得用戶授權(quán),才能訪問(讀/寫)用戶睡眠分析數(shù)據(jù)。要做到這一點,先 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ù)蘋果公司的文檔,每個睡眠分析樣本數(shù)據(jù)只含有一個值。HealthKit 使用兩個或以上在時間上重疊的樣本數(shù)據(jù),來表示用戶躺在床上并處于睡眠中。通過對比這些樣本數(shù)據(jù)的開始及結(jié)束時間,App 可以計算出一些二級統(tǒng)計數(shù)據(jù):

  • 用戶入睡所需要的時間
  • 用戶躺在床上的時間里,實際睡著的時間所占的百分比
  • 用戶仍躺在床上時,醒過來的次數(shù)
  • 用戶在床上度過的時間和睡眠時間的總和

簡單來說,你需要使用以下方法將睡眠分析數(shù)據(jù)存入 HealthKit Store:

  1. 我們需要定義兩個NSDate對象,對應(yīng)到開始時間與結(jié)束時間。
  2. 之后使用HKCategoryTypeIdentifierSleepAnalysis來創(chuàng)建一個HKObjectType實例。
  3. 我們需要創(chuàng)建一個類型為HKCategorySample的新對象。你通常使用類別樣本(Category Sample)來記錄睡眠數(shù)據(jù)。獨立樣本(Individual Sample)用來表示在某些時間段中,用戶躺在床上或處于睡眠中。所以我們需要創(chuàng)建一個記錄用戶躺在床上時間(In bed)的樣本與一個記錄用戶處于睡眠中(Asleep)的樣本,這兩個樣本可能會有部分時間重疊。
  4. 最后,我們使用HKHealthStore類中的saveObject方法來保存對象。

編輯提示:關(guān)于樣本的類型,你可以在HealthKit Constants Reference中找到更多詳細信息。

如果你想要將上述文字翻譯成 Swift 代碼,下面是部分代碼,用于保存睡眠分析數(shù)據(jù),包括在床上度過的時間數(shù)據(jù)和睡眠時間的數(shù)據(jù)。請將下列方法插入到ViewController中:

func saveSleepAnalysis() {
    
    // alarmTime 和 endTime 都是 NSDate 對象
    if let sleepType = HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis) {
        
        // 我們創(chuàng)建一個新的對象,并將它導(dǎo)入到 Health app 中
        let object = HKCategorySample(type:sleepType, value: HKCategoryValueSleepAnalysis.InBed.rawValue, startDate: self.alarmTime, endDate: self.endTime)
        
        // 最后,我們保存這個對象
        healthStore.saveObject(object, withCompletion: { (success, error) -> Void in
            
            if error != nil {
                // 錯誤處理
                return
            }
            
            if success {
                print("My new data was saved in HealthKit")
                
            } else {
                // 另一個錯誤處理
            }
            
        })
        
        
        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 {
                // 另一個錯誤處理
            }
            
        })
        
    }
    
}

我們想要將睡眠分析數(shù)據(jù)存入 HealthKit 時,可以調(diào)用這個函數(shù)。

讀取睡眠分析數(shù)據(jù)

為了讀取睡眠分析數(shù)據(jù),我們將創(chuàng)建一個請求。你需要從使用HKCategoryTypeIdentifierSleepAnalysis來創(chuàng)建一個HKObjectType類的實例開始。你可能想使用 predicate,通過startDateendDate這些詞來過濾獲取的數(shù)據(jù),這是一些對應(yīng)到某些時間段的NSDate對象。你也需要創(chuàng)建一個sortDescriptor來對獲取數(shù)據(jù)的請求進行排序,以選出你需要的結(jié)果。

用于獲取睡眠分析數(shù)據(jù)的代碼如下:

func retrieveSleepAnalysis() {
    
    // 首先,我們定義需要的對象類型
    if let sleepType = HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis) {
        
        // 使用 sortDescriptor 來獲取時間由近到遠的數(shù)據(jù)
        let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
        
        // 創(chuàng)建查詢請求,以及一個 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ù)以時間降序進行排序。之后每條請求打印出了開始時間與結(jié)束時間,并標(biāo)明是在床上度過的類型數(shù)據(jù),或是在睡眠狀態(tài)的類型數(shù)據(jù)。我將請求的時間設(shè)為30,來獲取過去30秒內(nèi)記錄的樣本。你也可以使用 predicate 方法來自定義開始與結(jié)束時間。

App 測試

在這個 demo 中,我使用一個NSTimer對象,在你點擊了開始按鈕之后進行計時。在開始的時候與結(jié)束的時候,我們各創(chuàng)建了一個NSDate對象,將睡眠分析數(shù)據(jù)保存為經(jīng)過的時間。在調(diào)用stop方法時,你可以調(diào)用saveSleepAnalysis()retrieveSleepAnalysis()來保存并獲取睡眠數(shù)據(jù)。

@IBAction func stop(sender: AnyObject) {
    endTime = NSDate()
    saveSleepAnalysis()
    retrieveSleepAnalysis()
    timer.invalidate()
}

在你的 App 中,你可能想要改變NSDate對象,為躺在床上和處于睡眠中這兩種狀態(tài)選擇(可能不同的)開始與結(jié)束時間。

在你做出這些改變之后,你可以運行這個 demo 并啟動計時器。讓它運行一段時間,然后點擊停止按鈕。做完這些之后,打開 Health App,你會發(fā)現(xiàn) App 中出現(xiàn)了睡眠數(shù)據(jù)。

一些關(guān)于 HealthKit App 的建議

HealthKit 提供了一個通用的平臺,通過這個平臺,App 開發(fā)者們可以方便地分享和訪問用戶數(shù)據(jù),避免獲得重復(fù)或不連續(xù)的數(shù)據(jù)。蘋果公司的審核指南中明確指出,對于那些使用了 HealthKit 并請求讀寫權(quán)限的 App,如果沒有明確演示它們的用途,可能會在審核時被拒絕。

那些保存了虛假或錯誤數(shù)據(jù)到 Health App 中的 App,可能也會被拒。這意味著你不能使用自己的算法來計算不同的健康數(shù)據(jù),比如這個教程中的睡眠分析數(shù)據(jù)。你應(yīng)該嘗試讀取內(nèi)建的傳感器數(shù)據(jù),并調(diào)整任意參數(shù)以避免計算出錯誤的數(shù)據(jù)。

你可以點擊這里獲取完整的 Xcode 項目。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容