HealthKit: Getting Fitness Data

This is the last installment in my series about HealthKit as we’re approaching the New Year and the release of the WATCH, especially the Sports edition. Make sure to take a look at my two previous posts about HealthKit:

Now, let’s talk about the heart of HealthKit – getting your user’s health data.

HKHealthStore

In case you haven’t read my HealthKit: Asking For Identifying Information post yet, the HKHealthStore class provides an interface for accessing and storing the user’s health data. Since this is what you’re using to read and write from the HealthKit database, it’s best to just create this once. I like to wrap by healthStore constant in a singleton, which I use to interface with HealthKit.

let healthStore: HKHealthStore? = {
    if HKHealthStore.isHealthDataAvailable() {
        return HKHealthStore()
    } else {
        return nil
    }
}()

Note that the HealthStore is not available on all devices, so make sure to do an HKHealthStore.isHealthDataAvailable() check when instantiating your healthStore constant.

HKQuantityType

As mentioned in my HealthKit: Let’s Talk About Units post, HKQuantity is a standardized way to talk about an amount of a specific unit (e.g. 120lb).

HKQuantityType helps us specify what exactly this quantity is referring to. For example, 120lb could be referring to someone’s weight or to how much weight they bench-pressed this morning at the gym.

In this post, I’ll focus on the getting the user’s step data:

let stepsCount = HKQuantityType.quantityTypeForIdentifier(
    HKQuantityTypeIdentifierStepCount)

This same code can be applied to all the other types of quantifiable Health data:

Asking for Permission

Just like with the user’s identifying information, we have to ask the user for permission to both READ and WRITE their HKQuantityType data.

let healthStore: HKHealthStore? = {
        if HKHealthStore.isHealthDataAvailable() {
            return HKHealthStore()
        } else {
            return nil
        }
    }()

let stepsCount = HKQuantityType.quantityTypeForIdentifier(
    HKQuantityTypeIdentifierStepCount)

let dataTypesToWrite = NSSet(object: stepsCount)
let dataTypesToRead = NSSet(object: stepsCount)   

healthStore?.requestAuthorizationToShareTypes(dataTypesToWrite,
readTypes: dataTypesToRead,
completion: { [unowned self] (success, error) in
            if success {
                println("SUCCESS")
            } else {
                println(error.description)
            }
        })

Now, when the user runs the app for the first time, they’ll see this screen pop up:

Notice that the user has very granular control over the permissions. They have to decide whether they want to allow you to just read their data or write to it! It is best practice to keep your READ and WRITE permissions separate. Really think about what your actually NEED for your app, and out of that whether you need both READ and WRITE permissions for each piece of data. The more data you ask for, the more the user has time to reconsider allowing permissions for your app!

Check out how overwhelming this page can get just with a few additional permission requests:

HealthKit Permissions Fitness

HKSampleQuery

To READ data from the HealthStore, simply create a query and execute it:

let stepsCount = HKQuantityType.quantityTypeForIdentifier(
    HKQuantityTypeIdentifierStepCount)

let stepsSampleQuery = HKSampleQuery(sampleType: stepsCount,
    predicate: nil,
    limit: 100,
    sortDescriptors: nil)
    { [unowned self] (query, results, error) in
        if let results = results as? [HKQuantitySample] {
            self.steps = results
            self.tableView.reloadData()
        }
        self.activityIndicator.stopAnimating()
}

// Don't forget to execute the Query!
healthStore?.executeQuery(stepsSampleQuery)

The HKSampleQuery can get very specific – you can ask for data with a specific filter and sorted in a specific order. The main thing to remember, though, is to make sure to execute the query!

If the user didn’t give you READ permissions for the data you’re asking, an empty results array is returned. If you do have READ permissions and the user has data entered, then you get back an array of HKQuantitySamples.

HKQuantitySample

An HKQuantitySample is the combination of an HKQuantity (e.g. 2,000 count) and an HKQuantityType (e.g. HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount). After all, you need to know what the quantity is referring to in terms of Health Data.

HKQuantitySample

Note that HKQuantitySample inherits from HKSample:

HKSample

This gets you the start and end date for your data point! Now you can enough information to populate a UITableViewCell with step and date data:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier(stepCellIdentifier) as UITableViewCell

    let step = steps[indexPath.row]
    let numberOfSteps = Int(step.quantity.doubleValueForUnit(HKUnit.countUnit()))

    cell.textLabel.text = "\(numberOfSteps) steps"
    cell.detailTextLabel?.text = dateFormatter.stringFromDate(step.startDate)

    return cell
}

The result is this in my case:

Steps Info

Hopefully you can come up with much cooler UI, especially for the WATCH!

HKStatisticsQuery

HealthKit also allows running of a statistical query. In my case, I’m just going to get the total steps the user has ever taken:

let stepsCount = HKQuantityType.quantityTypeForIdentifier(
    HKQuantityTypeIdentifierStepCount)

let sumOption = HKStatisticsOptions.CumulativeSum

let statisticsSumQuery = HKStatisticsQuery(quantityType: stepsCount, quantitySamplePredicate: nil,
    options: sumOption)
    { [unowned self] (query, result, error) in
        if let sumQuantity = result?.sumQuantity() {
            let headerView = self.tableView.dequeueReusableCellWithIdentifier(self.totalStepsCellIdentifier) as UITableViewCell

            let numberOfSteps = Int(sumQuantity.doubleValueForUnit(self.healthKitManager.stepsUnit))
            headerView.textLabel.text = "\(numberOfSteps) total"
            self.tableView.tableHeaderView = headerView
        }
        self.activityIndicator.stopAnimating()

}

// Don't forget to execute the query!
healthStore?.executeQuery(statisticsSumQuery)

The result is that I can now see the total number of steps in the header of my table view:

Total Steps

In addition to the CumulativeSum, you can also get the min, max, average, and data separated by source:

HKStatisticsOptions

One thing I’d like to point out is that at this time, Querying HealthKit is very very slow! So make sure you have clear indicators for your user’s that the query is actually happening, or consider querying in the background.

Writing (aka Sharing) Data

When we did a query for steps data, we got back an array of HKQuantitySamples. To WRITE data to the HealthKit database, we need to convert our data (3000 steps taken today) into an HKQuantitySample:

let stepsQuantityType = HKQuantityType.quantityTypeForIdentifier(
    HKQuantityTypeIdentifierStepCount)

let stepsUnit = HKUnit.countUnit()
let stepsQuantity = HKQuantity(unit: stepsUnit,
    doubleValue: 3000)

let stepsQuantitySample = HKQuantitySample(
    type: stepsQuantityType,
    quantity: stepsQuantity,
    startDate: NSDate(),
    endDate: NSDate())

Now you can WRITE your HKQuantitySample object to the HealthKit database, but only if the user has given you permission!

if let authorizationStatus = healthStore?.authorizationStatusForType(stepsQuantityType) {

    switch authorizationStatus {

    case .NotDetermined:
        requestHealthKitAuthorization()

    case .SharingDenied:
        showSharingDeniedAlert()

    case .SharingAuthorized:
        healthStore?.saveObject(stepsQuantitySample, withCompletion: { (success, error) -> Void in
            if success {
                // handle success
            } else {
                // handle error
            }
        })
    }
}

If you’re successful in writing the data point, the user will be able to fully control it in Apple’s Health app:

Note that the user can decide to delete your data point at any time! Or any other data point for that matter.

Responsibility

I’d like to leave you with a famous quote from Spiderman:

“With great power comes great responsibility.”

HealthKit has a lot of potential, and can really change how consumers think about their Health, especially with the upcoming release of the WATCH. But it all depends on user trust. When developing with HealthKit, remember to really honor the user. Ask only for the data you need, and treat the data with the outmost care. This is really personal data that the user is entrusting you with!

You can view the demo project from this post on Github here. As I mentioned earlier, querying HealthKit data is very very slow at the moment, so don’t be surprised if it takes a long time for the data to populate in the demo project!

Enjoy the article? Join over 14,500+ Swift developers and enthusiasts who get my weekly updates.

  • Aleksandar Vacić

    Have you tried using HKWorkout workoutWithActivityType: and its friends in 8.2? Save goes well (up to 8.1.1) but data are nowhere to be found in Health app. It’s a confirmed issue (I asked Apple).
    I sure this is fixed soon.

  • Srinivasa Kadiyala

    Hi Natasha:

    I was trying to use part of your code from GitHub, and it is complaining saying ‘Health Manager’ does not have a method named ‘stepsCount’.

    Second thing is that how can I get rid of the watch and show in simple tabular form as multiple rows with order by desc or asc?

    Thanks for sharing a nice example.

  • Brandi

    Hey, i had the same problem & thought health kit is just slow, but its not!

    The problem is that the queries are performed asynchronously in the background so if you want to do something in the completion block of the query, like saving data or updating the UI make sure to dispatch back to the main thread (or queue)!

    In swift this is really simple, just add:
    dispatch_async(dispatch_get_main_queue(), { () -> Void in
    //do something like saving data or updating the UI here
    })

    This should speed up your queries dramatically 🙂

  • Hanno Welsch

    thanks, Natasha.
    Just finished my query for weight data: needs 16 minutes to query 1200 weight data. And I have no chance to show some progress, cause building the query needs that time. Evaluating the results is a seconds job… I do it as a background task, but hard to tell the users that building a query needs such a long time. And it makes not difference to limit the query or use a predicate. I guess limiting and predicating is done after the query has finished.

    • yeah, when I was playing with this, I thought it just wasn’t working. Then I ran to the restroom, and was surprised to see results when I got back! So sad how slow this is! One option could be to tell the user to do something else and send them a notification when the query is finally done.

  • Sabes™

    I have the exact same method in my IOS app and my Watch app (Xcode 7 GM), in IOS it returns all my workouts, in Watchkit it only returns the first 2….is Apple throttling how much workout data I can pull down?

  • Hector

    What about if I ask permission for read 3 types, and then I update my app and now I need to read 4 types (1 more than the previous 3) do we need to show again the permission screen? and how can I ask permission again?