逐日更新关注:http://weibo.com/hanjunqiang 新浪微博!iOS开发者交换QQ群: 446310206
HeathKit框架学习
本文结构
- 简介
- 用户数据安全及隐私
- HeathKit框架
- HeathKit使用
- 总结
简介
HeathKit
是Apple公司在推出iOS 8 系统时1块推出的关于健康信息的框架。如果iPhone手机系统升级到iOS8以后就会发现多了1个健康-app
,这就是Apple提供的1个记录用户健康信息的app,可以用它来分享健康和健身数据。还可以指定数据的来源,比如我们自己创建1个app,在我们的app中使用了HeathKit
框架以后只要经过用户的认证,就能够在我们的app当中给健康
分享数据或从健康
中获得数据。
HeathKit
可以与健身装备1起工作,iPhone手机本身可以监控步数信息,会自动导入步数信息。但是其他信息或装备需要配套的应当才能获得到数据并导入到HeathKit
中并在健康
中显示。
HeathKit
不能再iPad中使用,而且它也不支持扩大。
用户数据安全及隐私
由于用户的健康信息多是敏感的,所以这些用户信息不能让开发者很随意的获得到。每条信息的读写都需要用户去选择是不是同意,比如用户可以同意你获得到用户的身高体重,但是不同意读写生殖健康等其他用户不愿意公然的信息。为了避免信息泄漏,我们是不知道用户是不是制止了某条信息是不是被用户制止读取的。简单的说,如果获得不到某条信息,就代表没有这条信息。
关于更多的关于隐私的信息,可以参考隐私
HeathKit框架
HeathKit
在各个利用之间提供了1种成心义的方式同享数据。因此,我们必须使用HeathKit
框架提供的数据类型和单位。这保证了数据存在的真正意义,我们不能自定义数据类型及单位。框架使用了子类化,例如HKObject
和HKObjectType
抽象类具有很多有平行关系的子类,当使用Object
或ObjectType
的时候,必须确保使用正确的子类。
在HeathKit
中能够存储的类都是HKObject
的子类,大部份HKObject
的子类都是不可变的。每一个对象都有下面的属性:
- UUID:每一个对象的标识符
- Source:数据的来源,来源可以是
HeathKit
的健康app,也能够是我们自己创建的app。当1个对象存储到HeathKit
中时会设置其来源。只有从HeathKit
中获得到的数据的来源才有效。 - Metadata:1个包括该对象额外信息的字典,元数据包括预定义的key和自定义的key,预定义的key用来帮助我们在利用间同享数据,而自定义的key用来扩大HeathKit,为对象添加针对利用的数据。
HeathKit
的对象主要分为特点和样本。特点对象代表用户的基本不变的数据,包括用户的生日、血型和性别等。我们创建的app不能修改这些信息,只能让用户在健康
中去修改或添加个人特点信息。
样本对象代表某个特定时间的数据,所有的样本类型的对象都是HKSample
的子类。它们都有下面的特性:
- Type :样本类型,例如:睡眠分享、步行距离、心率样本等
- StartDate:样本开始时间
- EndDate:样本结束时间。如果是某1个时间的样本,则开始于结束时间相同,如果是某个时间段的样本,则结束时间在开始时间的后面。
样本类型又可以分为4个类型:
- 种别样本(
HKCategorySample
):在iOS 8 中,只有睡眠分析这1个种别样本。代表有限种类的样本. - 数量样本(
HKQuantitySample
):这类样本代表存储数据的样本,比如步数、距离、用户的体温等。它是HeathKit
中最多见的数据类型。 - 关系样本(
HKCorrelation
):代表复合数据,包括1个或多个样本。在iOS 8 中,用correlation
代表食品和血压。在创建食品或血压时,需要用correlation
。 - 训练活动(
HKWorkout
):代表某种活动,比如走、跑步等。包括有开始时间、结束时间、运动类型、消耗能量、运动距离等属性。还可以为workout
关联许多详细的样本。不像correlation
,这些样本不包括在workout
中,但是可以通过workout
获得到。
再介绍1个HeathKit中常常用到的1些类。
HKSamle
每一个HkSample
的子类都有对应的便利方法创建对应的对象。比如:
对数量样本,需要创建HKQuantity
类的实例。而且数量的单位和类型标识符文档中描写的可用单位要相同。例如:HKQuantityTypeIdentifierHeight
文档中说明它使用长度单位,因此,你的数量必须使用厘米、米、英尺、英寸或其他长度单位。
对应种别样本,需要创建HKCategorySample
的实例。它的值必须和类型标识符文档中描写的枚举值相干。例如, HKCategoryTypeIdentifierSleepAnalysi
s 文档中说明它使用的枚举值。因此你在创建样本时必须从这个枚举中传递1个值。
一样,你必须先创建correlation包括的所有样本。correlation的类型标识符描写了它可以包括的类型和对象的数量。不要把被包括的对象存进HealthKit。它们是以correlation的1部份存储的。
逐日更新关注:http://weibo.com/hanjunqiang 新浪微博!iOS开发者交换QQ群: 446310206
对训练活动样本,首先,创建 HKWorkoutType
实例其实不需要指定类型标识符。所有的workout都是用一样的类型标识符。第2,对每一个workout
你都需要提供1个 HKWorkoutActivityType
值。这个值定义了workout
中履行的活动的类型。最后,当workout
保存到HealthKit
后,你可以给workout
关联额外的样本。这些样本提供了workout
的详细信息。
HKQuery
HeathKit提供了许多查询读取数据的方法:
- 直接方法查询。对特点样本,可以直接查询获得到,这些方法只能查询特点样本。更多信息: HKHealthStore Class Reference
样本查询。这是使用最多的查询。使用样本查询可以查询在HeathKit中任意的数据。而且可以对结果进行排序等。更多信息:HKSampleQuery Class Reference
视察者查询。这是1个长时间运行的查询,它会检测HealthKit存储,并在匹配到的样本产生变化时通知你。如果当存储产生变化时你想得到通知,就使用视察者查询。更多信息:HKObserverQuery Class Reference
- 锚定对象查询。用这类查询来搜索添加进存储的项。当锚定查询第1次履行时,会返回存储中所有匹配的样本。在接下来的履行中,只会返回上1次履行以后添加的项目。通常,锚定对象查询会和视察者查询1起使用。视察者查询告知你某些项目产生了变化,而锚定对象查询来决定有哪些(如果有的话)项目被添加进了存储。更多信息:HKAnchoredObjectQuery Class Reference
统计查询。使用这类查询来在1系列匹配的样本中履行统计运算。你可使用统计查询来计算样本的总和、最小值、最大值或平均值。更多信息: HKStatisticsQuery Class Reference
统计集合查询。使用这类查询来在1系列长度固定的时间间隔中履行屡次统计查询。通常使用这类查询来生成图表。查询提供了1些简单的方法来计算某些值,例如,每天消耗的总热量或每5分钟行走的步数。统计集合查询是长时间运行的。查询可以返回当前的统计集合,也能够监测HealthKit存储,并对更新做出响应。更多信息,参见 HKStatisticsCollectionQuery Class Reference。
Correlation查询。使用这类查询来在correlation查找数据。这类查询可以为correlation中每一个样本类型包括独立的谓词。如果你只是想匹配correlation类型,那末请使用样本查询。更多信息,参见 HKCorrelation Class Reference。
来源查询。使用这类查询来查找HealthKit存储中的匹配数据的来源(利用和装备)。来源查询会列出贮存的特定样本类型的所有来源。更多信息,参见HKSourceQuery Class Reference。
逐日更新关注:http://weibo.com/hanjunqiang 新浪微博!iOS开发者交换QQ群: 446310206
HKUnit
这个类代表要查询的数据的单位的类,比如体重的单位,可以为kg、lbs等。这个类为不同的数据类型提供了不同的单位方法。1般在创建前面介绍的样本类型的时候,都需要这个类为样本添加对应的单位。而且提供了1些数学运算,比如千米、米、厘米等之间的转换。
在某些场合,你可使用格式化器来本地化数量。iOS8提供了提供了新的格式化器来处理长度(NSLengthFormatter
)、质量(NSMassFormatter
)和能量(NSEnergyFormatter
)。对其他的数量,你需要自己来换算单位和本地化数据。
HKHeathStore
HeathKit的核心就是它,它代表HeathKit的数据库,使用它就能够从数据库中读取数据。比较重要的方法:
- isHealthDataAvailable:判断当前设置是不是支持HeathKit
- requestAuthorizationToShareTypes(typesToShare: Set?, readTypes typesToRead: Set?, completion: (Bool, NSError?) -> Void): 向用户要求同意读写某些数据
- saveObject(object: HKObject, withCompletion completion: (Bool, NSError?) -> Void) :向数据库中添加数据
- executeQuery(query: HKQuery) :履行查询,即上面介绍的几种查询方法。
HeathKit使用
在使用HealthKit
之前,必须要履行以下步骤:
- 打开
HeathKit
,在Target栏中,打开Capabilities
菜单,将HealthKit
这1项的开关设为ON的状态。 创建HeathManager.Swift
文件,并导入
`import HeathKit`
HeathKit
的核心是HeathStore
,创建
func authorizeHealthKit(completion:((success:Bool,error:NSError!)->Void)!){}
然后调用在这个方法中调用isHealthDataAvailable
判断当前装备是不是支持HeathKit
//判断当前装备是不是支持
if !HKHealthStore.isHealthDataAvailable(){
let error = NSError(domain: "", code: 2, userInfo: [NSLocalizedDescriptionKey:"HealthKit is not available in this Device"])
if completion != nil {
completion(success: false, error: error)
}
}
,最后在上面的方法中,设置要读写的数据类型。
为你的利用实例化1个 HKHealthStore 对象。每一个利用只需要1个HealthKit存储实例。这个存储实例就是你和HealthKit数据库交互的主要接口。
let hkHealthStore = HKHealthStore()
使用 requestAuthorizationToShareTypes:readTypes:completion:
来认证要求从HeathKit获得数据的权限。
//要求连接
hkHealthStore.requestAuthorizationToShareTypes(healthKitTypesToWrite as? Set<HKSampleType>, readTypes: healthKitTypesToRead as? Set<HKObjectType>) { (success, error) -> Void in
if completion != nil{
completion(success:success,error:error)
}
return
}
如果当前装备支持HeathKit的时候,这样就会弹出1个要求界面,让用户选择是不是同意你能够获得到你要要求的数据。
获得特点信息
我们首先创建了ProfileViewController.swift
,并用IB创建1个要求个人信息的界面
然后在HeathManager.Swift
文件中添加要求个人信息的方法。
对要求特点信息,条件上用户通过健康
添加了诞生日期、性别、血型等特点信息
func readProfile()->(age:Int?,biologicalsex:HKBiologicalSexObject?,bloodType:HKBloodTypeObject?){
//要求年龄
var age:Int?
let birthDay:NSDate;
do {
birthDay = try hkHealthStore.dateOfBirth()
let today = NSDate()
let diff = NSCalendar.currentCalendar().components(.Year, fromDate: birthDay, toDate: today, options: NSCalendarOptions(rawValue: 0))
age = diff.year
}catch {
}
//要求性别
var biologicalSex
:HKBiologicalSexObject?
do {
biologicalSex = try hkHealthStore.biologicalSex()
}catch {
}
//要求血型
var hkbloodType:HKBloodTypeObject?
do {
hkbloodType = try hkHealthStore.bloodType()
}catch{
}
return (age,biologicalSex,hkbloodType)
}
要求体重、身高、BMI的时候,创建另外的方法。
func fetchMostRecentSample(sample:HKSampleType,competion:((HKSample!,NSError!)->Void)!){
//1.创建谓词
let past = NSDate.distantPast()
let now = NSDate()
let mostRecentPredicate = HKQuery.predicateForSamplesWithStartDate(past, endDate: now, options: .None)
//2.创建返回结果排序的描写,是降序还是升序的,由于只需要1个结果,就设定限制为1个
let sortDescrptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate , ascending: false)
let limit = 1
//3.创建HKSampleQuery对象,
let sampleQuery = HKSampleQuery(sampleType: sample, predicate: mostRecentPredicate, limit: limit, sortDescriptors: [sortDescrptor]) { (sampleQuery, results, error) -> Void in
if let queryError = error {
competion(nil,queryError)
return
}
let mostRecentSample = results?.first
if competion != nil{
competion(mostRecentSample,nil)
}
}
//4.履行查询
self.hkHealthStore.executeQuery(sampleQuery)
}
获得以后在之前创建的ProfileViewController.swift
文件中获得这些信息,并更新UI。
对应特点信息,可以直接调用查询方法,并更新
let profile = healthManager?.readProfile()
self.healthStore = HKHealthStore()
ageLabel.text = profile?.age == nil ? kUnKnowString:String(profile!.age!)
sexLabel.text = biologicSexLiteral(profile?.biologicalsex?.biologicalSex)
bloodTypeLabel.text = bloodTypeLiteral(profile?.bloodType?.bloodType)
这里面创建了两个工具方法biologicSexLiteral
和bloodTypeLiteral
来修改查询的结果为我们想要的模样并显示在界面上。
对体重和身高,需要创建样本查询
/**
获得并更新体重
*/
func updateWeight(){
let weightSampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass)
self.healthManager?.fetchMostRecentSample(weightSampleType!, competion: { (mostRecentSample, error) -> Void in
if error != nil {
return
}
var weightString = self.kUnKnowString
self.weight = mostRecentSample as? HKQuantitySample
//根据我们想要的数据类型单位获得对应的结果
if let kilograms = self.weight?.quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(HKMetricPrefix.Kilo)){
//体重格式化
let weightFommater = NSMassFormatter()
weightFommater.forPersonMassUse = true
weightString = weightFommater.stringFromKilograms(kilograms)
}
//由于这个查询默许是异步查询的,所以需要在主线程更新UI
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.weightLabel.text = weightString
self.updateBMILabel()
}
})
}
/**
获得并更新身高
*/
func updateHeight(){
//设置要查找的类型,根据标识符
let heightSampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)
//获得身高样本
self.healthManager?.fetchMostRecentSample(heightSampleType!, competion: { (heightSample, error) -> Void in
if error != nil {
return
}
var heightStr = self.kUnKnowString
self.height = heightSample as? HKQuantitySample
//根据我们想要的数据类型单位获得对应的结果
if let kilograms = self.height?.quantity.doubleValueForUnit(HKUnit.meterUnit()){
heightStr = String(format: "%.2f", kilograms) + "m"
}
//由于这个查询默许是异步查询的,所以需要在主线程更新UI
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.heightLabel.text = heightStr
self.updateBMILabel()
}
})
}
对应BMI,它代表人的身体质量指数,它的计算方式是:体重/(身高*身高)。因此它可以这样取得
/**
获得并设置BMI:
*/
func updateBMILabel(){
//根据我们想要的数据类型单位获得对应的结果
let weight = self.weight?.quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(HKMetricPrefix.Kilo))
let height = self.height?.quantity.doubleValueForUnit(HKUnit.meterUnit())
var bmiValue = 0.0
if height == 0{
return
}
dispatch_async(dispatch_get_main_queue()) { () -> Void in
bmiValue = (weight!)/(height! * height!)
self.BMILabel.text = String(format: "%.02f", bmiValue)
}
}
添加BMI到HeathStore
在下面的方法中添加1个alertView
让用户输入BMI值,然后点击确认按钮以后添加到HeathStore
中
@IBAction func addBMIData2HealthStore(sender: AnyObject) {
let alertView = UIAlertController(title: "输入BMI值", message: nil, preferredStyle: .Alert)
alertView.addTextFieldWithConfigurationHandler { (textField) -> Void in
textField.keyboardType = .NumberPad
}
let action = UIAlertAction(title: "添加", style: .Default) { (action) -> Void in
var value:Double?
if let text = alertView.textFields?.first?.text {
if text.characters.count > 0 {
value = Double(text)
self.saveBMI2HealthStore(value!)
}
}
}
alertView.addAction(action)
self .presentViewController(alertView, animated: true, completion: nil)
}
//保存BMI到heathKitStore中
func saveBMI2HealthStore(height:Double){
//BMI的类型
let BMIType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMassIndex)
//根据标识符对应的单位创建BMI的数量对象
let BMIQuantity = HKQuantity(unit: HKUnit.countUnit(), doubleValue: height)
let now = NSDate()
//根据起止时间和上面创建的创建HKQuantity对象创建数量样本
let BMISample = HKQuantitySample(type: BMIType!, quantity: BMIQuantity, startDate: now, endDate: now)
//保存数量样本到healthStore中
self.healthStore?.saveObject(BMISample, withCompletion: { (success, error) -> Void in
if success {
print("添加成功")
self.updateWeight()
}
if (error != nil) {
print("添加失败")
}
})
}
如果添加成功,你就能够去手机上的健康
查找BMI,就能够看到我们刚才添加的BMI值,而且它的来源是我们创建的app。
获得HKWorkout
创建1个WorkOutsViewController.swift
文件,并在SB中拖对应的IB文件,界面以下
然后在在HeathManager.Swift
文件中添加要求workout的方法
/**
获得workoutData
*/
func fetchWorkOutsData(completion:([AnyObject]!,NSError!)->Void){
let workOutsSampleType = HKSampleType.workoutType()
let workOutsPredicate = HKQuery.predicateForWorkoutsWithWorkoutActivityType(.Running)
let sortDescrptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate , ascending: false)
let workOutsQuery = HKSampleQuery(sampleType: workOutsSampleType, predicate: workOutsPredicate, limit: 0, sortDescriptors: [sortDescrptor]) { (workoutsQuery, results, error) -> Void in
if (error != nil){
print("获得失败")
return
}
if results != nil{
completion(results!,nil)
}
}
self.hkHealthStore.executeQuery(workOutsQuery)
}
然后在WorkOutsViewController.swift
文件的viewWillAppear()
方法中要求workout
self.healthManager?.fetchWorkOutsData({ (results, error) -> Void in
if error != nil{
print("获得失败")
}else{
self.workOuts = results as! [HKWorkout]
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
});
})
最后在tableView显示以下
保存HKWorkout
在上面的界面的NavgationBar
的rightBarItem
向下再拖1个控制器,并添加对应的文件AddWorkoutsViewController.swift
,并在IB中设置界面信息以下
然后在 AddWorkoutsViewController.swift
复写 tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
,针对点击不同的cell,履行不同的方法。即让用户输入点击的cell对应的输入方式,比如时间就是时间选择器。距离就是1个警示框加1个文本框等。
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
self.tableView.tableFooterView = UIView()
switch indexPath.row{
case 0:
self.setupPickerView()
case 1,2:
self.setupDatePickerView(indexPath.row)
case 3,4:
self.setupAlertView(indexPath.row)
default:
break
}
}
这里面对应的选择的方法就不逐一介绍了,就是几个普通的view的添加。添加完所有的信息以后就能够点击done
保存信息,方法以下:
@IBAction func addWorkOut(sender: AnyObject) {
self.heathStore = HKHealthStore()
//获得距离和能量的数值
let distanceValue = Double(self.distanceLabel.text!)
let energyValue = Double(self.energyLabel.text!)
//根据上面的数值创建对应的HKQuantity对象
let distance = HKQuantity(unit: HKUnit.mileUnit(), doubleValue: distanceValue!)
let energy = HKQuantity(unit: HKUnit.calorieUnit(), doubleValue: energyValue!)
let endDate = self.dateFommater?.dateFromString(self.endDateLabel.text!)
let startDate = self.dateFommater?.dateFromString(self.startDateLabel.text!)
//这里我默许设置成running了。可以根据具体的类型再进行设置。
//创建HKWorkout对象。
let workout = HKWorkout(activityType: .Running, startDate: startDate!, endDate: endDate!, workoutEvents: nil, totalEnergyBurned: energy, totalDistance: distance, metadata: nil)
//保存上面创建的HKWorkout对象
self.heathStore?.saveObject(workout, withCompletion: { (success, error) -> Void in
if error != nil{
print("添加毛病")
return
}
if success{
print("添加成功")
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.dismissViewControllerAnimated(true, completion: nil)
})
}
})
}
如果上面都履行成功,AddWorkoutsViewController.swift
就会模态消失,然后在上面的1个页面`WorkOutsViewController.swift
就会在tableView的最上层显示出我们刚才添加成功的HKWorkout
总结
在本人过完春节回到公司上班以后经理问我健康
app里面的信息能不能获得到。之前只是简单了解了这个框架,但是里面的具体结构体系其实不了解。就趁着项目不忙,抽空把HeathKit学习了解了1下。本文的demo也采取了之前自学的Swift简单的实现了1下(属于Switer新手)。可能会有毛病或不准确的地方,如果你看到了,可以给我联系ls_xyq@126.com,我会及时更改的。写这篇文章1是对HeathKit的学习的1个练习,在这也是赐与后会用到的童鞋1个可以参考的东西。
HeathKit不只是上面的这些内容,但是能把上面的这些问题弄定,我觉得针对HeathKit的体系会有1个清楚的认识,学习HeathKit更深层次的内容会有很大的帮助。
本文的demo已放到github上面,需要的同学可以下载看看。
本文参考文章:
- HealthKit框架参考
- HealthKit开发教程Swift版
- The HealthKit Framework
逐日更新关注:http://weibo.com/hanjunqiang 新浪微博!iOS开发者交换QQ群: 446310206