介绍HealthKit

在此HealthKit文章中,您将学习如何请求访问HealthKit数据的权限,以及如何向中央HealthKit存储库读写数据。 本文使用Swift 4,iOS 11,Xcode 9版本。

HealthKit是iOS 8中引入的API。HealthKit充当所有与健康相关的数据的中央存储库,允许用户创建生物学档案并存储锻炼数据。

阅读HealthKit文章时,您将创建最简单的培训跟踪应用程序并学习:

  • 如何请求权限和访问HealthKit数据
  • 如何读取HealthKit数据并将其显示在UITableView中
  • 如何将数据写入中央HealthKit存储库

准备好开始使用HealthKit了吗? 继续阅读!

注意: 要学习本教程,您将需要一个有效的iOS开发人员帐户。 否则,您将无法启用HealthKit功能并无法访问HealthKit存储库。

开始


入门应用程序在锻炼程序期间跟踪卡路里燃烧。 对于好莱坞内部人士和名流而言,很明显,我在谈论Prancercise

实践

下载入门项目并在Xcode中打开它

编译并运行该应用程序。 您将看到用户界面的“骨架”。 在接下来的两篇文章中,您将逐步为该应用程序添加功能。

图片

分配团队


HealthKit是一个特殊的框架。 如果您没有有效的开发人员帐户,则该应用程序将无法使用它。 拥有开发者帐户后,您可以分配您的团队。

在项目浏览器中选择PrancerciseTracker ,然后选择PrancerciseTracker target 。 转到常规选项卡,然后单击团队字段。

选择与您的开发者帐户关联的命令:

图片

权限/权利


HealthKit也具有自己的一组权限,您需要启用它们才能创建使用该框架的应用程序。

在目标编辑器中打开“ 功能”选项卡并启用HealthKit ,如以下屏幕截图所示:

图片

等待Xcode为您配置HealthKit。 通常,这里没有问题,但是如果忘记正确指定组和捆绑包标识符,仍然会遇到一些问题。

现在一切就绪。 您只需要询问用户使用HealthKit的权限即可。

权限


HealthKit可处理机密和敏感数据。 并不是每个人都感到很舒服,以至于允许已安装的应用程序访问此信息。

这就是HealthKit具有强大的隐私系统的原因。 HealthKit仅有权访问用户同意共享的数据。 要为Prancercise Tracker的用户创建配置文件,必须首先获得访问每种数据类型的权限。

用法说明更新


首先,您需要描述为什么要求用户提供健康指标。 Xcode使您能够在应用程序的Info.plist文件中指定此代码。

打开Info.plist 。 然后添加以下键:
隐私 - 健康共享使用说明
隐私 -运行状况更新使用情况说明

这些键存储将在HeathKit登录屏幕出现时显示的文本。 Health Share使用说明是指应从HealthKit读取的数据部分。 运行状况更新使用情况说明与写入HealthKit的数据相对应。

您可以在此处“添加”所需的所有内容。 这通常是一个描述:“我们将使用您的健康信息来更好地跟踪您的锻炼情况。”

请记住,如果未安装这些密钥,则当您尝试登录HealthKit时应用程序将崩溃。

HealthKit授权


打开HealthKitSetupAssistant.swift文件,其中是用于在HealthKit中进行授权的类方法。

class func authorizeHealthKit(completion: @escaping (Bool, Error?) -> Swift.Void) { } 

authorizeHealthKit(completion :)方法不带任何参数,并且具有返回布尔值( 成功失败 )和可选错误(如果万一出问题)的补全。 如果返回错误,则在两种情况下将它们传递给complition:

  1. HealthKit可能在您的设备上不可用。 例如,如果应用程序在iPad上运行。
  2. 在当前版本的HealthKit中,某些数据类型可能不可用。

让我们打破这个过程。 要授权HealthKit, authorizeHealthKit(completion :)方法必须完成以下四个步骤:

  1. 检查Healthkit在此设备上是否可用。 如果不是这种情况,则将失败和错误返回至完成状态。
  2. 准备健康数据类型。 Prancercise Tracker将读取和写入HealthKit。
  3. 将此数据组织为要读取的类型和要写入的类型的列表。
  4. 请求授权。 如果此操作成功,则所有活动均正确并正确完成。

HealthKit可用性检查


首先,您需要检查设备上HealthKit的可用性。
将以下代码粘贴到authorizeHealthKit(completion :)方法的开头:

 //1. ,      HealthKit guard HKHealthStore.isHealthDataAvailable() else { completion(false, HealthkitSetupError.notAvailableOnDevice) return } 

您会经常与HKHealthStore互动。 它是存储用户健康数据的中央存储库。 isHealthDataAvailable()方法将帮助您了解当前用户设备是否支持Heathkit数据。

如果HealthKit在设备上不可用,则guard语句会阻止应用程序执行其余的authorizeHealthKit(completion :)方法。 发生这种情况时,会用notAvailableOnDevice错误调用完成块。 您可以将其简单地输出到控制台或主控制器中,以在发生此类错误时处理进一步的步骤。

资料准备


一旦知道HealthKit在用户的设备上可用,就该准备将要读写HealthKit的数据类型了。
HealthKit使用HKObjectType类型。 进入或返回到中央HealthKit存储库的每种类型都是一种HKObjectType 。 您还将看到HKSampleType和HKWorkoutType 。 它们都继承自HKObjectType ,因此基本上这是同一件事。

在第一个代码段之后立即粘贴以下代码段:

 //2.   ,     HealthKit guard let dateOfBirth = HKObjectType.characteristicType(forIdentifier: .dateOfBirth), let bloodType = HKObjectType.characteristicType(forIdentifier: .bloodType), let biologicalSex = HKObjectType.characteristicType(forIdentifier: .biologicalSex), let bodyMassIndex = HKObjectType.quantityType(forIdentifier: .bodyMassIndex), let height = HKObjectType.quantityType(forIdentifier: .height), let bodyMass = HKObjectType.quantityType(forIdentifier: .bodyMass), let activeEnergy = HKObjectType.quantityType(forIdentifier: .activeEnergyBurned) else { completion(false, HealthkitSetupError.dataTypeNotAvailable) return } 

哇,这是个大后卫 ! 这也是使用单个防护程序检索多个选项的一个很好的例子。

若要为这些特征创建HKObjectType ,您需要使用HKObjectType.characteristicType(forIdentifier :)HKObjectType.quantityType(forIdentifier :)
特征类型和数量类型是框架定义的枚举。 HealthKit与他们一起穿靴。

您还将注意到,如果一种特性或选择类型不可用,则该方法将失败。 这是故意的。 您的应用程序应该始终确切知道可以使用哪种类型的HealthKit。

准备用于读取和写入的数据类型列表


现在该准备一份用于读取和写入的数据类型列表了。
在第二部分之后立即将第三个代码粘贴到authorizeHealthKit(completion :)方法中:

 //3.   ,  HealthKit     let healthKitTypesToWrite: Set<HKSampleType> = [bodyMassIndex, activeEnergy, HKObjectType.workoutType()] let healthKitTypesToRead: Set<HKObjectType> = [dateOfBirth, bloodType, biologicalSex, bodyMassIndex, height, bodyMass, HKObjectType.workoutType()] 

HealthKit期望一组代表用户可以写入的数据类型的HKSampleType对象,并且还期望为您的应用程序显示一组HKObjectType对象。

HKObjectType.workoutType()HKObjectType的特殊类型。 它是任何锻炼。

HealthKit授权


最后一部分是最简单的。 您只需要从HealthKit请求授权。 粘贴最后一段代码:

 //4.    HKHealthStore().requestAuthorization(toShare: healthKitTypesToWrite, read: healthKitTypesToRead) { (success, error) in completion(success, error) } 

此代码从HealthKit请求授权,然后调用完成。 它们将变量用于成功操作以及从HKHealthStore的requestAuthorization(toShare:read:complete :)方法传递的错误。

您可以将其视为重定向。 您无需将数据包传递给HealthKitSetupAssistant内部,而是将数据包传递给主控制器,该主控制器可能显示警告或采取其他措施。

该项目已经有一个Authorize HealthKit按钮,并在MasterViewController中调用authorizeHealthKit()方法。 这是调用我们刚刚编写的授权方法的理想场所。

打开MasterViewController.swift ,找到authorizeHealthKit( )方法并粘贴以下代码:

 HealthKitSetupAssistant.authorizeHealthKit { (authorized, error) in guard authorized else { let baseMessage = "HealthKit Authorization Failed" if let error = error { print("\(baseMessage). Reason: \(error.localizedDescription)") } else { print(baseMessage) } return } print("HealthKit Successfully Authorized.") } 

此代码使用您刚刚实现的authorizeHealthKit(completion :)方法。 完成后,它将在控制台中显示一条消息,指示在HealthKit中授权是否成功。

启动应用程序。 在主窗口中单击“授权HealthKit”,您将看到一个弹出授权屏幕:

图片

打开所有开关,滚动查看所有开关,然后按允许 。 在控制台中,您应该看到以下消息:

 HealthKit Successfully Authorized. 

太好了! 该应用程序可以访问HealthKit的中央存储库。 现在是时候开始跟踪项目了。

特性和样品


在本节中,您将学习:

  • 如何阅读用户的生物学特征。
  • 如何读写不同类型的样本(重量,高度等)

通常,生物学特性是不会改变的元素类型,就像您的血型一样。 样品是经常变化的元素,例如重量。

为了正确跟踪Prancercise锻炼模式的有效性, Prancercise Tracker应用程序必须接收用户体重和身高的样本。 这些样本一起可用于计算体重指数(BMI)。

注意: 体重指数(BMI)是人体脂肪的一种广泛使用的指标,是根据一个人的体重和身高计算出来的。 在此处了解更多信息。

阅读规范


Prancercise Tracker不记录生物学特征。 他从HealthKit获得它们。 这意味着这些特征必须首先存储在中央HeathKit存储库中。

如果您还没有这样做,是时候告诉HeathKit更多有关您自己的信息。

在设备或模拟器上打开“运行状况”应用程序。 选择“健康数据”选项卡。 然后,单击右上角的配置文件图标以查看您的健康配置文件。 单击编辑,然后输入信息的出生日期,性别,血型:

图片

现在, HealthKit知道您的出生日期,性别和血型,是时候在Prancercise Tracker中阅读这些功能了。

返回Xcode并打开ProfileDataStore.swiftProfileDataStore类代表您对用户所有与健康相关的数据的访问点。

将以下方法粘贴到ProfileDataStore中

 class func getAgeSexAndBloodType() throws -> (age: Int, biologicalSex: HKBiologicalSex, bloodType: HKBloodType) { let healthKitStore = HKHealthStore() do { //1. This method throws an error if these data are not available. let birthdayComponents = try healthKitStore.dateOfBirthComponents() let biologicalSex = try healthKitStore.biologicalSex() let bloodType = try healthKitStore.bloodType() //2. Use Calendar to calculate age. let today = Date() let calendar = Calendar.current let todayDateComponents = calendar.dateComponents([.year], from: today) let thisYear = todayDateComponents.year! let age = thisYear - birthdayComponents.year! //3. Unwrap the wrappers to get the underlying enum values. let unwrappedBiologicalSex = biologicalSex.biologicalSex let unwrappedBloodType = bloodType.bloodType return (age, unwrappedBiologicalSex, unwrappedBloodType) } } 

getAgeSexAndBloodType()方法调用HKHealthStore ,询问用户的出生日期,性别和血型。 它还使用出生日期计算用户的年龄。

  1. 您可能已经注意到,此方法可能会导致错误。 只要在HealthKit中央存储库中没有存储生日,性别或血型,就会发生这种情况。 由于您刚刚在应用程序中输入了此信息,因此不会引起错误。
  2. 使用Calendar类,可以将任何日期转换为一组Date Components 。 如果您想获得一年的约会日期,这真的很方便。 该代码仅获取您的出生年份,当前年份,然后计算差异。
  3. “扩展”变量的命名方式很显然,您需要从包装类( HKBiologicalSexObjectHKBloodTypeObject )中访问基本枚举。

UI更新


如果现在编译并运行该应用程序,则您尚未在用户界面中看到任何更改,因为尚未将此逻辑连接到它。
打开ProfileViewController.swif t并找到loadAndDisplayAgeSexAndBloodType )方法

此方法将使用您的ProfileDataStore将生物学特征加载到用户界面中。

将以下代码粘贴到loadAndDisplayAgeSexAndBloodType()方法中:

 do { let userAgeSexAndBloodType = try ProfileDataStore.getAgeSexAndBloodType() userHealthProfile.age = userAgeSexAndBloodType.age userHealthProfile.biologicalSex = userAgeSexAndBloodType.biologicalSex userHealthProfile.bloodType = userAgeSexAndBloodType.bloodType updateLabels() } catch let error { self.displayAlert(for: error) } 

该代码块将年龄,性别和血液类型加载为元组。 然后,他在UserHealthProfile模型的本地实例中设置这些字段。 最后,它通过调用updateLabels()方法,使用UserHealthProfile中的新字段更新用户界面。

由于ProfileDataStore的 getAgeSexAndBloodType()方法可能会引发错误,因此ProfileViewController必须对其进行处理。 在这种情况下,您只需采取错误并将其显示为警告。

这一切都很棒,但有一个陷阱。 updateLabels()方法尚未执行任何操作。 这只是一个空白广告。 这次,让我们进入用户界面。

找到updateLabels()方法并将此代码粘贴到其中:

 if let age = userHealthProfile.age { ageLabel.text = "\(age)" } if let biologicalSex = userHealthProfile.biologicalSex { biologicalSexLabel.text = biologicalSex.stringRepresentation } if let bloodType = userHealthProfile.bloodType { bloodTypeLabel.text = bloodType.stringRepresentation } 

代码很简单。 如果用户设置了年龄,它将被格式化为标签。 生物性别和血型也是如此。 变量stringRepresentation将枚举转换为字符串以用于显示。

编译并运行该应用程序。 转到配置文件和BMI屏幕。 单击读取HealthKit数据按钮。

图片

如果您以前在应用程序中输入了信息,则该信息应显示在此屏幕的快捷方式中。 如果您尚未这样做,则会出现一条错误消息。

哇! 您可以直接从HealthKit读取和显示数据。

查询样本


现在是时候读取用户的体重和身高了。 它们将用于在配置文件视图中计算和显示BMI。

生物学特性很容易获得,因为它们几乎不会改变。 样品需要更复杂的方法。 他们使用HKQuery ,更确切地说是HKSampleQuery

要从HealthKit请求样品,您将需要:

  1. 指定您要索取的样品类型(重量,高度等),
  2. 一些有助于过滤和排序数据的其他选项。 为此,可以传递一个可选的NSPredicateNSSortDescriptors数组。

注意: 如果您熟悉CoreData,您可能会注意到一些相似之处。 HKSampleQuery与对象类型的NSFetchedRequest非常相似,您可以在其中指定谓词和排序描述符,然后设置对象的上下文以执行查询以获取结果。

设置查询后,只需调用HKHealthStore ExecuteQuery()方法即可获取结果。

对于Prancercise Tracker,您将创建一个通用功能,该功能可以下载任何类型的最新样本。 因此,您可以将其用于体重和身高。

打开ProfileDataStore.swift并将以下方法粘贴到该类中,位于getAgeSexAndBloodType()方法的正下方:

 class func getMostRecentSample(for sampleType: HKSampleType, completion: @escaping (HKQuantitySample?, Error?) -> Swift.Void) { //1.  HKQuery    . let mostRecentPredicate = HKQuery.predicateForSamples(withStart: Date.distantPast, end: Date(), options: .strictEndDate) let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false) let limit = 1 let sampleQuery = HKSampleQuery(sampleType: sampleType, predicate: mostRecentPredicate, limit: limit, sortDescriptors: [sortDescriptor]) { (query, samples, error) in //2.       . DispatchQueue.main.async { guard let samples = samples, let mostRecentSample = samples.first as? HKQuantitySample else { completion(nil, error) return } completion(mostRecentSample, nil) } } HKHealthStore().execute(sampleQuery) } 

此方法使用样品的类型(身高,体重,BMI等)。 然后,他创建一个查询以检索此类型的最后一个样本。 如果您查看增长样本,您将返回上一个增长记录。

这里发生了很多事情。 我将停止解释一些事情。

  1. HKQuery中有几种方法可以帮助您过滤出HealthKit查询示例。 在这种情况下,我们使用内置的日期谓词。
  2. 从HealthKit请求样品是一个异步过程。 这就是为什么在Dispatch块内找到完成处理程序中的代码的原因。 主线程上需要合规。 否则,应用程序将失败。

如果一切顺利,您的请求将被执行,并且您将在线程上返回一个简洁的示例, ProfileViewController可以在该示例中将其内容放置在标签中。 现在开始做这部分。

在用户界面中显示样本


在上一节中,您是从HealthKit下载数据的。 将它们另存为ProfileViewController中的模型,然后使用ProfileViewController updateLabels()方法更新行中的内容

您所要做的就是通过添加一个函数来扩展此过程,该函数可以加载样本,为用户界面处理它们,然后调用updateLabels()来用文本填充标签。

打开ProfileViewController.swift文件,找到loadAndDisplayMostRecentHeight )方法并粘贴以下代码:

 //1.  HealthKit      guard let heightSampleType = HKSampleType.quantityType(forIdentifier: .height) else { print("Height Sample Type is no longer available in HealthKit") return } ProfileDataStore.getMostRecentSample(for: heightSampleType) { (sample, error) in guard let sample = sample else { if let error = error { self.displayAlert(for: error) } return } //2.     ,   , //    . let heightInMeters = sample.quantity.doubleValue(for: HKUnit.meter()) self.userHealthProfile.heightInMeters = heightInMeters self.updateLabels() } 

  1. 此方法首先创建一种生长样本。 然后,他将这种类型的样本传递给您刚刚编写的方法,该方法将返回HealthKit中记录的最新用户增长样本。
  2. 一旦样本返回,增长将转换为米并存储在UserHealthProfile模型中。 然后, UI将更新。

注意: 通常您希望将数量样本转换为标准单位。 为此,以上代码使用doubleValue(for :)方法,该方法允许您将所需的适当数据(在本例中为meter )传递给HKUnit

您可以使用HealthKit提供的一些常见类方法来创建各种类型的HKUnit 要获取计数器,您只需使用HKUnit中的meter()方法,这将是您所需要的。

随着增长的整理。 重量呢? 一切都非常相似,但是您需要在ProfileViewController中填充loadAndDisplayMostRecentWeight ()方法。

将以下代码粘贴到loadAndDisplayMostRecentWeight()方法中:

 guard let weightSampleType = HKSampleType.quantityType(forIdentifier: .bodyMass) else { print("Body Mass Sample Type is no longer available in HealthKit") return } ProfileDataStore.getMostRecentSample(for: weightSampleType) { (sample, error) in guard let sample = sample else { if let error = error { self.displayAlert(for: error) } return } let weightInKilograms = sample.quantity.doubleValue(for: HKUnit.gramUnit(with: .kilo)) self.userHealthProfile.weightInKilograms = weightInKilograms self.updateLabels() } 

您创建想要接收的样本类型,为其请求HealthKit ,进行一些单位转换,将其保存在模型中并更新用户界面。

目前,它可以表明工作已完成,但是还有其他事情。 updateLabels()函数无法识别您已经可以使用的新数据。

让我们修复它。

将以下行添加到updateLabels()函数中,在展开血型以在用户界面上显示它的部分的正下方:

 if let weight = userHealthProfile.weightInKilograms { let weightFormatter = MassFormatter() weightFormatter.isForPersonMassUse = true weightLabel.text = weightFormatter.string(fromKilograms: weight) } if let height = userHealthProfile.heightInMeters { let heightFormatter = LengthFormatter() heightFormatter.isForPersonHeightUse = true heightLabel.text = heightFormatter.string(fromMeters: height) } if let bodyMassIndex = userHealthProfile.bodyMassIndex { bodyMassIndexLabel.text = String(format: "%.02f", bodyMassIndex) } 

遵循updateLabels()函数中的原始模板,它扩展了UserHealthProfile模型中的身高,体重和体重指数。 如果可用,它们将生成适当的行并将其分配给用户屏幕上的标签。

MassFormatterLengthFormatter完成将值转换为字符串的工作。

体重指数实际上并未存储在UserHealthProfile模型中。 这是一个为您执行计算的计算属性。

单击bodyMassIndex属性,您将明白我的意思:

 var bodyMassIndex: Double? { guard let weightInKilograms = weightInKilograms, let heightInMeters = heightInMeters, heightInMeters > 0 else { return nil } return (weightInKilograms/(heightInMeters*heightInMeters)) } 

体重指数是一个可选属性,也就是说,如果您未指定身高或体重(或者将它们设置为没有意义的数字),则它可以返回nil。 实际计算只是重量除以高度的平方。

注意: 如果您还没有将数据添加到HealthKit以便应用程序读取的话,那么您很快就会沉迷于所有这些。如果尚未这样做,则至少需要创建身高和体重样本。

打开运行状况应用程序,然后转到运行状况数据选项卡。在此处,选择“身体测量”参数,然后选择“权重”,然后选择“添加数据点”以添加新的体重样本。重复增长过程。

此时,Prancercise Tracker应该能够读取用户体重和身高的最新样本,然后将其显示在文本中。

编译并运行应用程序。转到配置文件和BMI。然后单击“ 读取HealthKit数据”按钮

图片

太棒了!您只需从HealthKit存储库中读取第一个样本,然后使用它们来计算BMI。

保存样品


Prancercise跟踪器已经拥有一个方便的计算身体质量指数。让我们用它来记录用户的BMI示例。

打开ProfileDataStore.swift并添加以下方法:

 class func saveBodyMassIndexSample(bodyMassIndex: Double, date: Date) { //1. ,      guard let bodyMassIndexType = HKQuantityType.quantityType(forIdentifier: .bodyMassIndex) else { fatalError("Body Mass Index Type is no longer available in HealthKit") } //2.   HKUnit     let bodyMassQuantity = HKQuantity(unit: HKUnit.count(), doubleValue: bodyMassIndex) let bodyMassIndexSample = HKQuantitySample(type: bodyMassIndexType, quantity: bodyMassQuantity, start: date, end: date) //3.      HealthKit HKHealthStore().save(bodyMassIndexSample) { (success, error) in if let error = error { print("Error Saving BMI Sample: \(error.localizedDescription)") } else { print("Successfully saved BMI Sample") } } } 

与其他样本类型一样,您必须首先确保HealthKit中提供了样本类型

  1. 在这种情况下,代码检查体重指数是否存在数量类型如果是这样,则将其用于创建数量样本。如果不是,则该应用程序停止工作。
  2. count() HKUnit , , . - , , .
  3. HKHealthStore , . , .

快完成了总结用户界面。

打开ProfileViewController.swif,找到saveBodyMassIndexToHealthKit(方法。当用户单击表中的“保存BMI”按钮时,将调用此方法。

将以下代码粘贴到该方法中:

 guard let bodyMassIndex = userHealthProfile.bodyMassIndex else { displayAlert(for: ProfileDataError.missingBodyMassIndex) return } ProfileDataStore.saveBodyMassIndexSample(bodyMassIndex: bodyMassIndex, date: Date()) 

您还记得,体重指数是一个计算属性,当从HealthKit加载身高和体重样本时,该属性返回一个值。这段代码正在尝试计算此属性,如果可能,它将被传递给您刚刚编写saveBodyMassIndexSample(bodyMassIndex:date :)方法

如果由于某种原因无法计算出体重指数,也会显示一个方便的警报。

编译并运行该应用程序。转到配置文件和BMI屏幕。从HeathKit下载数据,然后单击“保存BMI”按钮。

查看控制台。你看到了吗?

    BMI 

如果是这样,恭喜!您的BMI样本现在存储在HealthKit中央存储库中让我们看看是否可以找到他。

打开“健康”应用程序,点击“健康数据”选项卡,在表格视图中单击“身体测量”,然后单击“身体质量指数”。

图片

Source: https://habr.com/ru/post/zh-CN434978/


All Articles