In earlier tutorials we have focussed on reading information with HealthKit. There are a number of queries that can be used to request information in a variety of ways. In this tutorial we will work on saving information with HealthKit.
Saving data to the health store with HealthKit follows a general procedure which has just 4 steps.
The first step is to find the identifier for the type of data you want to store. For this tutorial we will save body mass. In a later tutorial we will look at saving workouts, and other more complex data types such as step counts where there are more than just 1 reading per day.
Preparing and Saving Data with HealthKit
Begin by downloading the starter project found here.
func saveBodyMass(date: Date, bodyMass: Double) { let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass) let bodyMass = HKQuantitySample.init(type: quantityType!, quantity: HKQuantity.init(unit: HKUnit.pound(), doubleValue: bodyMass), start: date, end: date) healthStore.save(bodyMass) { success, error in if (error != nil) { print("Error: \(String(describing: error))") } if success { print("Saved: \(success)") } } }
The sample code above shows the general process for saving data with HealthKit. It does the following:
The function name includes 2 parameters. Because we are saving bodyMass, the start and end dates would be identical. So the first parameter it requests is a date. The second is a double value which would be the weight of the user.
On line 2 we create our quantity type. Finding what we need to do here is the key to being successful with the later save. We first need to visit the Health Data Types page at Apple. In here we can find exactly what how to create this. If you look for bodyMass on this page, you will see that it is a quantity type. What we do is use the convenience method of HKObjectType to create the quantity type… that is we use HKObjectType.quantityType(forIdentifier:). We then specify bodyMass using the HKQuantityTypeIdentifier structure.
Line 3 is where we create the actual bodyMass that we are going to save. This is a HKQuantitySample which we call the init method on. We pass in the type (from line 2).
On line 4, which is a continuation of line 3, we need to specify the quantity. Here we use the initialiser of HKQuantity and specify we want pounds. You might prefer KG or Stones depending on what you most commonly use. We then pass it a double value which was passed in as a parameter on the function.
Next we pass in the date to both the start and end time. Typically for bodyMass you would use the same date because bodyMass is just a snapshot of what you weighed when you recorded the sample.
Next, we call the save method on the healthStore (line 7). This has a completion handler which lets you know if the save was successful or not. Also note that there are two different save methods on HKHealthStore. One of them accepts a single HKObject, and the other accepts an array of HKObjects should you want to save more than one sample at a time. Remember that HKQuantitySample is a subclass of HKObject, and thus, can be saved with this method.
The next step is to make sure that we have sufficient permissions from the user to save this data to the health store.
On line 36 of the starter project, add the following:
let writeDataTypes : Set = [HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!]
We will pass this in to the requestAuthorisation on line 38 which will look as follows:
healthStore.requestAuthorization(toShare: writeDataTypes, read: readDataTypes) { (success, error) in if !success { // Handle the error here. } else { } }
The final step is to then make a call from the success side of the if statement to call our function we created earlier we do this as follows:
self.saveBodyMass(date: Date(), bodyMass: 189.4)
That goes on line 42 of the sample code. It passes in the date as it is now, and then passes in the bodyMass of 189.4. You clearly wouldn’t hard code this value in a real life situation, but this is an example of how it works.
When you run the app, you will get a message in the console saying that the save was successful. If you keep running the app, it will keep on saving. To delete the data from Apple Health, you can either delete each item manually, or just delete the test app and then hit delete when it asks if you want to also delete any data it added. The app will only remove the samples of 189.4 that it added, but will not touch any of your own personal data stored by other apps.
Things to Consider for Performance
Body mass is fairly straight forward. You step on the scale, you open up your chosen app, and you record your weight. If you are more fancy than that, you might have a wifi scale that automatically records your weight to Apple Health for you. However the data is recorded, it’s typically 1 sample a day usually, if that.
But, there maybe times when you work with more complicated data such as data that records samples over a period of time. Think of step counts. Every few minutes, Apple analyses the accelerometer data of both your watch and phone, and then records this in Apple Health as your step count. When Apple built this they had a number of options to choose from. Do they record every single step as a sample, or do they combine them in to a sample? If combined, how often do they combine? Apple opted to combine steps. If you look at your step counts you will see a bunch of 1, 2, or 3 digit step counts being recorded in samples.
As part of performance, Apple recommends that you figure out what is best for the user. Sometimes it might work best to have more samples, but sometimes it might work best to have less. You need to balance out performance in these cases to make it work for the user in terms of speed and how detailed the data is, but at the same time, not overwhelm them and the device with thousands of data points.
Other Recommendations
When a sample is saved in to Apple Health, that sample no longer belongs to your app. The user could remove permission, or allow another app to edit, remove, change, etc… the data that your app created. You need to keep this in mind when developing HealthKit apps. Just because your app saved a particular type of data, it doesn’t mean that this data will be there in the weeks to come. For this reason, Apple recommends using long-running queries so that your app is notified if any data changes. As an alternative to this, Apple recommends you run a new query each time you need to draw a chart just so that you are charting with the latest data and not charting samples that no longer exist.
Closing
Todays tutorial looked at saving a quantity sample. These can be put in to an array if you have more, and passed to the save method on healthStore as an array. In a future tutorial we will look at saving other types of data, particularly categories and correlations.
The finished sample project can be downloaded here.
The full sample code is below:
import UIKit import HealthKit class ViewController: UIViewController { let healthStore = HKHealthStore() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. if HKHealthStore.isHealthDataAvailable() { var readDataTypes : Set<HKObjectType> = [] if #available(iOS 9.3, *) { readDataTypes = [HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!, HKObjectType.characteristicType(forIdentifier: HKCharacteristicTypeIdentifier.biologicalSex)!, HKObjectType.characteristicType(forIdentifier: HKCharacteristicTypeIdentifier.dateOfBirth)!, HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!, HKObjectType.activitySummaryType()] } else { // Fallback on earlier versions readDataTypes = [HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!, HKObjectType.characteristicType(forIdentifier: HKCharacteristicTypeIdentifier.biologicalSex)!, HKObjectType.characteristicType(forIdentifier: HKCharacteristicTypeIdentifier.dateOfBirth)!, HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!] } let writeDataTypes : Set = [HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!] healthStore.requestAuthorization(toShare: writeDataTypes, read: readDataTypes) { (success, error) in if !success { // Handle the error here. } else { self.saveBodyMass(date: Date(), bodyMass: 189.4) } } } } func saveBodyMass(date: Date, bodyMass: Double) { let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass) let bodyMass = HKQuantitySample.init(type: quantityType!, quantity: HKQuantity.init(unit: HKUnit.pound(), doubleValue: bodyMass), start: date, end: date) healthStore.save(bodyMass) { success, error in if (error != nil) { print("Error: \(String(describing: error))") } if success { print("Saved: \(success)") } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
Leave a Reply
You must be logged in to post a comment.