通过 Swift 使用 HealthKit 中的睡眠汇总

本文由海之号角(OceanHorn)翻译自Using Sleep Analysis in HealthKit with Swift

当今睡眠革命是一种时尚,用户比以往更好奇,不仅限于他们已经睡了多长时间,也关心通过收集一段时间的数据后分析展现出的睡眠趋势。包括硬件,尤其是手机的技术进步已经为这个日益增长的主题带来了新的曙光。

Apple 通过嵌入 Health 应用提供了一种非常酷的方式来安全的传输和存储用户的健康信息。你不光可以使用 HealthKit 搭建一款健身应用,也可以通过这个框架来访问睡眠统计数据。

在本教程中我将向你简单介绍 HealthKit 框架,并向你展示怎样搭建一款简单的睡眠汇总应用。

前言

HealthKit 框架通过一个加密数据库提供了用于存储数据的架构,称为 HealthKit store 。你可以通过使用 HKHealthStore类访问这个数据库。iPhone 和 Apple Watch 有各自的 HealthKit store。健康数据在 Apple Watch 和 iPhone 间同步。但是 Apple Watch 中的过期数据会被周期性的清除以便节省存储空间。 HealthKit 框架和 Health 应用在 iPad 上不可用。

如果你想基于健康数据创建一款 iOS 或 watchOS 应用, HealthKit 是强有力的工具。 HealthKit 被设计用来管理来自广泛来源的数据,并且会基于用户设置自动的合并来自不同来源的数据。应用也可以访问每一个数据源的原始数据,然后自己合并。不只是身体测量、健身数据或者营养摄入,数据也可以被用在睡眠状况中。

本文的剩余部分我将向你展示怎么使用 HealthKit 框架在 iOS 中保存和访问睡眠汇总数据。同样的方法在 watchOS 应用中也适用。 请注意本教程的环境是 Swift2.0 和 Xcode7 。所以请确保使用 Xcode7 或更新版本来学习本教程。( 译者注: Swift2.2 及 Xcode7.3.1 可以运行通过。 )

在开始之前,请下载并解压初始项目。我已经为你实现了 UI 及基本的功能。当你运行初始项目时,在按开始按钮之后将会看到一个显示了已经开始多久的计时器 UI 。

HealthKit 框架的使用

我们这款应用的功能是通过 StartStop 按钮保存睡眠数据以及检索数据。要使用 HealthKit 框架,必须首先保证你的包中包含了 HealthKit 。在工程中找到 Targets -> Capabilities ,打开 HealthKit 的开关。

HealthKit-allow-1240x775
HealthKit-allow-1240x775

然后,你需要在 ViewController 类中通过下面的代码创建一个 HKHealthStore 的实例:

let healthStore = HKHealthStore()

之后我们将使用 HKHealthStore 实例来访问 HealthKit store中的数据。

像之前提到的那样,HealthKit 保证了用户能够控制自己的健康数据。所以你必须在访问(读/写)用户睡眠数据前首先获得用户的允许。为了实现这个目的,首先导入 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")
        }
    }
}

上面的代码将提示用户允许或拒绝权限请求。在完成回调中,你可以处理成功或错误信息,并获得最终结果。当用户赋予应用所有权限时这段代码不是必须的。你必须在你的应用中优雅的处理这些错误。
但是对于测试目的,你必须选择 “Allow” 来保证应用能够访问到你设备上的健康数据。

Health-App-Permission
Health-App-Permission

写入睡眠汇总数据

首先,我们怎样才能读取到睡眠汇总数据? 根据苹果文档,每次睡眠统计采样数据都只能有一个值。为了表示用户在床休息和睡眠状态,HealthKit 使用了两个或更多的时间轴上重叠的采样数据。通过比较这些采样数据的开始和结束时间,第三方应用可以计算出一系列的二次统计数据。

  • 用户进入睡眠所经过的时间

  • 睡眠时间占在床休息时间的百分比

  • 用户睡醒后依然在床休息的时间

  • 在床休息与睡眠的总时间

record_sleep_data
record_sleep_data

简而言之,你可以按照下面的方法来保存睡眠汇总数据到 HealthKit store 中:

  1. 定义两个 NSDate 对象来对应开始时间与结束时间。

  2. 使用 HKCategoryTypeIdentifierSleepAnalysis 来创建一个 HKObjectType 实例。

  3. 创建 HKCategorySample 类型的对象。一般的可以使用分类采样来记录睡眠数据。单独的采样数据分别来代表用户在床休息与睡眠的时间区间。我们将创建在时间轴上重叠的在床休息采样与睡眠时间采样。

  4. 最后,我们使用 HKHealthStoresaveObject 来保存对象。

编辑注:对于采样的类型,可以查阅 HealthKit Constants Reference

如果你将上面转化为 Swift 语言,就会得到下面保存在床休息与睡眠时间的睡眠汇总数据的代码片段。请将下面的方法插入 ViewControllerl 类中:

func saveSleepAnalysis() {

    // alarmTime and endTime are NSDate objects
    if let sleepType = HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis) {

        // we create our new object we want to push in Health app
        let object = HKCategorySample(type:sleepType, value: HKCategoryValueSleepAnalysis.InBed.rawValue, startDate: self.alarmTime, endDate: self.endTime)

        // at the end, we save it
        healthStore.saveObject(object, withCompletion: { (success, error) -> Void in

            if error != nil {
                // something happened
                return
            }

            if success {
                print("My new data was saved in HealthKit")

            } else {
                // something happened again
            }

        })


        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 {
                // something happened
                return
            }

            if success {
                print("My new data (2) was saved in HealthKit")
            } else {
                // something happened again
            }

        })

    }

}

上面的函数会在我们想将睡眠汇总数据保存到 HealthKit 时调用。

读取睡眠汇总数据

要读取睡眠汇总数据,我们需要创建一个查询。首先需要通过定义 HKObjectType 分类到 HKCategoryTypeIdentifierSleepAnalysis 。你或许也会想使用谓词通过 startDateendDate 来过滤检索到的数据,而这些你想检索到的数据都是对应时间范围的 NSDate 对象。你也许需要创建一个 sortDescriptor 来排序整理获取到的数据来得到想要的结果。

你读取睡眠汇总数据的代码将会是下面这样:

func retrieveSleepAnalysis() {

    // first, we define the object type we want
    if let sleepType = HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis) {

        // Use a sortDescriptor to get the recent data first
        let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)

        // we create our query with a block completion to execute
        let query = HKSampleQuery(sampleType: sleepType, predicate: nil, limit: 30, sortDescriptors: [sortDescriptor]) { (query, tmpResult, error) -> Void in

            if error != nil {

                // something happened
                return

            }

            if let result = tmpResult {

                // do something with my data
                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)")
                    }
                }
            }
        }

        // finally, we execute our query
        healthStore.executeQuery(query)
    }
}

上面的代码查询 HealthKit 中所有的睡眠汇总数据然后按照降序排列。然后将每个查询到的结果的开始时间、结束时间以及类型(在床休息或睡眠时间)打印输出。代码中我限制为30个数据,将展示最后30条记录。你也可以使用谓词方法来自定义开始和结束时间。

应用测试

在demo应用中我使用了一个计时器来显示点击开始按钮后的时间。在开始和结束按钮按下后分别创建一个 NSDate 对象来计算时间长度作为睡眠汇总数据。在 stop 方法中调用 saveSleepAnalysis()retrieveSleepAnalysis() 方法来保存和获取睡眠数据。

在你的应用中,你或许会想改变NSDate对象来选择相关的开始和结束时间(很可能不同)来保存在床休息时间和睡眠时间。
一旦改变了这些,你可以打开demo应用然后开始计时器。运行一段时间后点击结束按钮。在这之后打开 Health 应用,你将会看到记录的睡眠数据。

sleep-analysis-test-1240x878
sleep-analysis-test-1240x878

关于HealthKit应用的一些建议

HealthKit被设计用来作为应用开发者分享和方便访问用户数据,并且避免任何可能的数据重复或不一致的通用平台。苹果审核指南特别针对使用HealthKit及请求读写权限而没有明确说明使用目的的应用将导致应用被拒绝。

向Health应用中保存虚假数据的应用也将会被拒绝。这意味着你计算不同的健康数据的算法不能像本指南中的睡眠汇总这样幼稚。你应该使用嵌入的传感器数据并调整可能的参数来避免计算出错误数据。

你可以从这里获取到完整的Xcode项目。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容