HealthKit: Let’s Talk About Units

It’s coming on that time of year again, when people are starting to come up with their New Year’s Resolution, which usually involve some king of fitness goal. But 2015 will be extra special. It’s the first year of the Apple Watch! And Apple has decided that fitness is one of the biggest use-cases for their Watch. So much so, that they created a whole line of watches specifically for sports, which are tripped out with sensors that monitor heart-rate.

With WatchKit coming out this month (yay!), we as developers have the opportunity to to be the first ones to develop for this new device that is almost guaranteed to be a success. So while we wait for the WatchKit debut, I wanted to write a bit about HealthKit for those interested in making health-related apps for the upcoming Apple Watch.

So I’m sure all of you heard about HealthKit, but for those of you who are not clear about what exactly it is (it wasn’t clear to me until I looked into it), HealthKit is an interface for a global (aka Apple devices only of course) Health database. This means that any app can write and read to this database (given user permission of course). So your app can use nutrition data tracked by the Panera app for example.

panera

To use this database, we have to all have a common language to talk about things like units and quantities, which is this first blog post about HealthKit will be about…

HKUnit

HKUnit lets developers standardize how we talk about units. There are two ways to instantiate a unit:

let cmUnit = HKUnit(fromString: "cm")

Here is a chart of the appropriate combinations you can have for instantiating HKUnit fromString:

Note that if you instantiate the wrong unit from string, your app will crash! So if your units are unpredictable, consider using the second way:

let cmUnit = HKUnit.meterUnitWithMetricPrefix(.Centi)
let inchUnit = HKUnit.inchUnit()

Here is the documentation of all the other units you can instantiate officially:

So when should you use fromString vs the official unit? My answer is to always think of readability. For example, HKUnit(fromString: "cm") is a lot more readable at a glance than HKUnit.meterUnitWithMetricPrefix(.Centi). However, HKUnit.inchUnit() or HKUnit.ounceUnit() is really readable and easy to instantiate without having to remember exactly the right fromString combination.

HKQuantity

An HKQuantity is a standardized way to talk about an amount of a specific unit. So for example, we can talk about 120cm as follows:


let cmUnit = HKUnit(fromString: "cm")

let length = HKQuantity(unit: cmUnit, doubleValue: 120)

Unit Conversion

While HKQuantity is instantiated with a specific unit in mind, it is very easy to do unit conversion with it:

let cmUnit = HKUnit(fromString: "cm")
let length = HKQuantity(unit: cmUnit, doubleValue: 120)

let inchUnit = HKUnit.inchUnit()
let lengthInInches = length.doubleValueForUnit(inchUnit)
// 47.244094488189

However, keep in mind that not all units can be converted. Take for example a cm to gram conversion:

Screen Shot 2014-11-12 at 7.36.45 AM

As you can see, this will crash your app! So if you’re in a situation when you’re unsure whether a unit can be converted to the unit you need, it’s very easy to check for compatibility:

let cmUnit = HKUnit(fromString: "cm")
let length = HKQuantity(unit: cmUnit, doubleValue: 120)

let gramUnit = HKUnit.gramUnit()

if length.isCompatibleWithUnit(gramUnit) {
    let lengthInGrams = length.doubleValueForUnit(gramUnit)
    println("The length is \(lengthInGrams) grams")
} else {
    println("length cannot be converted to grams!")
}

// length cannot be converted to grams!

Localization

I think this is my favorite part of HealthKit as a developer. With only a few lines of code, your app can be easily understood by people from all over the world.

Apple has added three new formatters specifically for this purpose:

  • NSMassFormatter
  • NSEnergyFormatter
  • NSLengthFormatter

All of these formatters function the same way, so I’m just going to show off the NSMassFormatter as an example:

let massFormatter = NSMassFormatter()

// if the mass is in relation to a person,
// set this property to true.
// I'm not a unit conversion expert, but
// I think in some countries, when you talk about
// a person's weight vs an object, it's different
// (I heard Canada is an example - 
// see Reference #4: http://en.wikipedia.org/wiki/Weight#References)
massFormatter.forPersonMassUse = true

// You can set this to
// .Short, .Medium, or .Long
// I recommend playing with this in
// a Playground to see the difference
massFormatter.unitStyle = .Long

let localizedWeight = massFormatter.stringFromKilograms(100)
// 220.462 pounds (localized to the US)

Sometimes, you might want just the unit string without the number (for example, when the user is filling out a form to input the units).

let massFormatter = NSMassFormatter()
massFormatter.forPersonMassUse = true
massFormatter.unitStyle = .Long

var massFormatterUnit = NSMassFormatterUnit.Kilogram
massFormatter.unitStringFromKilograms(100, usedUnit: &massFormatterUnit)
// "pounds"

// you can also get the singular version:
massFormatter.unitStringFromKilograms(1, usedUnit: &massFormatterUnit)
// pound

// notice that the massFormatterUnit is now a Pound
massFormatterUnit == NSMassFormatterUnit.Pound // true

// once the user enters a number into the form, 
// you can get the correct string for the unit
massFormatter.stringFromValue(100, unit: massFormatterUnit)
// "100 pounds"

So there you have it, with only a few lines of code, you can now have a standardized way of thinking about units and quantities, with a full unit conversion and localization included!

Want to play and test out HealthKit units? Check out my HealthKit Playground on Github. Enjoy!

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

  • shashank shekhar

    Hi. Thanks for the interesting and informative article. I have a couple of noob questions.

    1. If there is only one global health db and all the third party app will be writing into that DB then how is the db going to be organized. Can app A get the data put in there by app B ?

    2. how will it be connected to the apple watch. Can app B use the data on the watch as its input that has been put in the database by app A using a phone.

    Thanks for the article again!

    • Here is an example of how to get user’s personal data from the HealthKit database: http://natashatherobot.com/healthkit-asking-for-identifying-information/. I’ll be writing about getting things like step count in my next HealthKit article, so hopefully that clarifies it. But yes, you’ll be able to get data added by a different app, and even separate it by source if you want.

      I’m guessing the first version of the Apple Watch will work through the phone. So it will likely transmit health data to the phone, which will then make the call to save the data to the HealthKit database. Again, HealthKit is just an interface for a database, so it’ll work the same way that any other API / Database integration will work with the Apple Watch. WatchKit should be out shortly, so we’ll know more then.

  • Chris

    Is there a way to check if the string is a Unit? When I am testing a false unit, it crashes

    • Chris

      further explaination:

      let unit = HKUnit(fromString: “notUnit”)

  • J@y

    So in Swift 2.0, I picked something up and not sure if this is a bug or whether they’ve just made it that way. But is there any chance someone else is noticing the same thing?

    Here’s the code

    let massFormatterTwo = NSMassFormatter()
    massFormatterTwo.forPersonMassUse = true
    massFormatterTwo.unitStyle = .Long
    var massFormatterUnit = NSMassFormatterUnit.Kilogram
    massFormatterTwo.unitStringFromKilograms(100, usedUnit: &massFormatterUnit)
    massFormatterTwo.unitStringFromKilograms(1, usedUnit: &massFormatterUnit)

    In the playground, there is no plural and singular string. Rather, both outputs are plural such as “pounds”.

    Let me know if you pick the same thing up. I haven’t seen any bug mentioned anywhere on the Internet.

    Thanks for posting these tutorials on HealthKit!

    • 1kg is 2.2 “pounds” or thereabouts. That’s what’s happening. Try entering 0.45359237.

  • Tudup Ka

    If your usage of HKUnit is not Health app related, Apple will reject your app based on:

    27.6 Details
    Your app uses the HealthKit APIs but does not indicate integration with the Health app in your Application Description and clearly identify the HealthKit functionality in your app’s UI, as required by the App Store Review Guidelines.

    • idStar

      Tudup, I just had the same rejection issue.

      Apple provides a wonderful unit conversion facility within Health Kit, but then won’t let you use it unless you integrate with the Health App? It seems like a really short sighted rule. I’m presenting information for medical clinicians who might want to see weight in kg or lbs, or height in cm or inches (and more complex quantities too). I have no need to integrate with the Health app. And for that, we get rejected.

      I really do think someone higher up in App Review needs to re-think these policies. It seems like such a waste of excellent API.

      • Tudup Ka

        If you do not use it for Health Kit related app. Then you have to take it out.

        I went back and forth with Apple and they said – no you cannot use it just for unit conversion.

        It is not the answer you want, but the faster you recognize it the faster you’ll re-write it.

        • idStar

          Thanks Tudup…time to get started re-writing 🙁

  • MDBritt

    I am trying to wrap my head around the lines that say:

    var massFormatterUnit = NSMassFormatterUnit.Kilogram
    massFormatter.unitStringFromKilograms(100, usedUnit: &massFormatterUnit)
    // This outputs “pounds” ???

    If I wanted pounds, why did I ask for a NSMassFormatterUnit of .Kilogram? When I use .Pound, I also get “pounds”. So it seems to me that either the massFormatterUnit is useless (it doesn’t change the outcome because the outcome is always local) or it is wrong.

    Obviously, there is some bug in my understanding so any insight would be appreciated.