Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

HealthKit Tutorial with Swift: Getting Started

$
0
0

Learn about the new HealthKit API in iOS 8!

HealthKit is a new API in iOS 8 that provides an elegant way to store and retrieve a user’s health data.

In this HealthKit tutorial, you’ll create a simple workout tracking app. In the process, you’ll learn a ton about HealthKit, such as:

  • How to request permission to access HealthKit data
  • How to read information and format it to show it in the screen
  • How to write data back to HealthKit

Ready to get a strong HealthKit workout? Read on!

Note: To work through this tutorial, you’ll need an active iOS developer account. Without one, you won’t be able to enable the HealthKit Capability and access the HealthKit Store.

Getting Started

You’re going to build a simple app that’ll request authorization to use HealthKit, and then read and write HealthKit data. The starter project already has all the user Interface in place so you can focus on HealthKit functionality.

Download the starter project and open it in Xcode.

Build and run the app. You’ll see the guts of an app that reads and writes Workout and Quantity samples:

Base Project UI

In the next sections, you’ll build up this project so that it does all of the following:

  • Requests authorization to use HealthKit
  • Reads user characteristics
  • Reads and saves quantity samples
  • Reads and saves workouts

Before you do anything else, you must change the project Application Bundle Identifier and assign your Team.

Select HKTutorial in the Project Navigator, and then select the HKTutorial target. Select the General tab and then change the Bundle Identifier to use your own name or domain name.

Then, in the Team combo box, select the team associated with your developer account:

Change Bundle ID and Team

So far so good!

Entitlements

In order to use HealthKit, you need to add the HealthKit entitlement.

Open the Capabilities tab in the target editor, and then turn on the switch in the HealthKit section, as shown in the screenshot below:

Add HealthKit Capability

Wait for Xcode to finish configuring things for you, and once it’s done your entitlements are set up. That was easy, eh?

Permissions

Remember your app doesn’t automatically get access to HealthKit data – you need to ask for permission. That’s what you’ll do in this section.

First, open HealthManager.swift and look inside. You’ll find an empty class.

This is where you’ll add all the HealthKit related code this project needs; it will act as the gateway for other classes to interact with the HealthKit store. And good news: you’ve already got an instance of this class in all necessary view controllers, so there’s no need to set that up.

Start by importing the HealthKit framework. Still in HealthManager.swift, add the following line just below the comment block up top:

import HealthKit

The core of the HealthKit Framework is the class HKHealthStore, and you need an instance of that class as well. Insert this line inside the HealthManager:

let healthKitStore:HKHealthStore = HKHealthStore()

Now that you’ve created an instance of HKHealthStore, the next step is to request authorization to use it.

Remember how the user is the master of their data, and controls which metrics you can track? This means you don’t request global access to the HealthKit store, rather, you request access to the specific types of objects your app needs to read or write to the store.

All the object types are subclasses of HKObjectType, and it provides convenience methods to create these subclasses.

You just need to invoke one of the methods with a constant to represent the specific type requested. Below is a list of these convenience methods that cover each of the types mentioned above. Don’t do anything here in Xcode, just watch and learn:

class func quantityTypeForIdentifier(identifier: String!) -> HKQuantityType!  // to get a Quantity Type
class func categoryTypeForIdentifier(identifier: String!) -> HKCategoryType!  // to get a Category Type
class func characteristicTypeForIdentifier(identifier: String!) -> HKCharacteristicType! // to get a Characteristic type
class func correlationTypeForIdentifier(identifier: String!) -> HKCorrelationType! // to get a CorrelationType
class func workoutType() -> HKWorkoutType! // to get a Workout type

The identifier you use in these methods must be one of the pre-defined constants in HealthKit, like HKQuantityTypeIdentifierHeight for the quantity type for height measurements, or HKCharacteristicTypeIdentifierBloodType to get the characteristic type for blood type.

The workout type does not need an identifier, because it has no sub-types.

Now back to coding! Open HealthManager.swift and add this method inside the HealthManager:

func authorizeHealthKit(completion: ((success:Bool, error:NSError!) -> Void)!)
{
  // 1. Set the types you want to read from HK Store
  let healthKitTypesToRead = NSSet(array:[
    HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierDateOfBirth),
    HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierBloodType),
    HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierBiologicalSex),
    HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass),
    HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight),
    HKObjectType.workoutType()
    ])
 
  // 2. Set the types you want to write to HK Store
  let healthKitTypesToWrite = NSSet(array:[
    HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMassIndex),
    HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned),
    HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning),
    HKQuantityType.workoutType()
    ])
 
  // 3. If the store is not available (for instance, iPad) return an error and don't go on.
  if !HKHealthStore.isHealthDataAvailable()
  {
    let error = NSError(domain: "com.raywenderlich.tutorials.healthkit", code: 2, userInfo: [NSLocalizedDescriptionKey:"HealthKit is not available in this Device"])
    if( completion != nil )
    {
      completion(success:false, error:error)
    }
    return;
  }
 
  // 4.  Request HealthKit authorization
  healthKitStore.requestAuthorizationToShareTypes(healthKitTypesToWrite, readTypes: healthKitTypesToRead) { (success, error) -> Void in
 
    if( completion != nil )
    {
      completion(success:success,error:error)
    }
  }
}

Let’s go over the code above, step by step.

  1. You’re creating an NSSet with all the types you need to read from the HealthKit store for this tutorial. Characteristics (blood type, sex, birthday), samples (body mass, height) and workouts.
  2. You create another NSSet that includes all the types needed to write to the store (workouts, BMI, energy burned, distance).
  3. Here you check if the HealthKit store is available and return an error if it’s not. For universal apps, this is crucial because HealthKit may not be available on every device. At the time of writing, you can’t use it on an iPad.
  4. Performs the actual authorization request; it invokes requestAuthorizationToShareTypes:readTypes with the previously defined types for read and write.

Now that your code knows how to request authorization, you need to create a way for your app to invoke it.

The starter project already has an Authorize HealthKit button for this, and it invokes the method authorizeHealthKit() in MasterViewController. That sounds like the perfect place to get your app talking to itself.

Open MasterViewController.swift, locate authorizeHealthKit() and replace the line:

println("TODO: Request HealthKit authorization")

With this:

healthManager.authorizeHealthKit { (authorized,  error) -> Void in
  if authorized {
    println("HealthKit authorization received.")
  }
  else
  {
    println("HealthKit authorization denied!")
    if error != nil {
      println("\(error)")
    }
  }
}

This code invokes the request authorization from authorizeHealthKit and shows a message in the console with the result.

Build and run. Click Authorize HealthKit in the main view and you’ll see this screen pop up:

Authorize HealthKit

Turn on all the switches and click Done. You’ll see a message like this in Xcode’s console:

HealthKit authorization received.

Great! Your app has access to the store. Prepare to plunge deeper into the world of HealthKit!

Characteristics and Samples

In this section, you’ll learn:

  • How to read user characteristics
  • How to read and write samples of different types.

All the fun happens in ProfileViewController. In this view controller, you’ll read characteristics (birth day, age and blood type), and query for weight and height samples.

After that, you’ll perform a calculation with said samples (in this case, the Body Mass Index, BMI), and save that calculated sample to the Store.

Note: Body Mass Index (BMI) is a widely used indicator of body fat, and it’s calculated from the weight and height of a person. Learn more about it here.

Reading Characteristics

Before you can read user characteristics, you need to make sure the information is in the HealthKit store. Hence, you need to feed it some data.

Open the Health App on your device or simulator. Select the Health Data tab, then in the list select Me. Now click Edit and add information for the Birth Date, Biological Sex and Blood Type.

Enter whatever you like, even your ultra-clever alter ego’s stats or the kitty’s stats will do.

Add Health Data

Your next task is to build the framework to read those characteristics.

Go back to Xcode, open HealthManager.swift and add the following method to the bottom of the HealthManager class:

func readProfile() -> ( age:Int?,  biologicalsex:HKBiologicalSexObject?, bloodtype:HKBloodTypeObject?)
{
  var error:NSError?
  var age:Int?
 
  // 1. Request birthday and calculate age
  if let birthDay = healthKitStore.dateOfBirthWithError(&error)
  {
    let today = NSDate()
    let calendar = NSCalendar.currentCalendar()
    let differenceComponents = NSCalendar.currentCalendar().components(.YearCalendarUnit, fromDate: birthDay, toDate: today, options: NSCalendarOptions(0) )
    age = differenceComponents.year
  }
  if error != nil {
    println("Error reading Birthday: \(error)")
  }
 
  // 2. Read biological sex
  var biologicalSex:HKBiologicalSexObject? = healthKitStore.biologicalSexWithError(&error);
  if error != nil {
    println("Error reading Biological Sex: \(error)")
  }
  // 3. Read blood type
  var bloodType:HKBloodTypeObject? = healthKitStore.bloodTypeWithError(&error);
  if error != nil {
    println("Error reading Blood Type: \(error)")
  }
 
  // 4. Return the information read in a tuple
  return (age, biologicalSex, bloodType)
}

This method reads the user’s characteristics from the HealthKit store, and returns them in a tuple. How it works:

  1. It invokes dateOfBirthWithError() to read the birthday from HKHealthStore. The next lines perform a calendar calculation to determine age in years.
  2. biologicalSexWithError() determines biological sex.
  3. Blood type is read with the method bloodTypeWithError().
  4. Lastly, all the values return in a tuple.

If you were to build and run now, you wouldn’t see any change to the characteristics in the UI because you’ve not opened the portal for the app and store to share the data yet.

Open ProfileViewController.swift and locate updateProfileInfo().

You’ll need to invoke this method when you click on the button Read HealthKit Data. So replace the line:

println("TODO: update profile Information")

with:

let profile = healthManager?.readProfile()
 
ageLabel.text = profile?.age == nil ? kUnknownString : String(profile!.age!)
biologicalSexLabel.text = biologicalSexLiteral(profile?.biologicalsex?.biologicalSex)
bloodTypeLabel.text = bloodTypeLiteral(profile?.bloodtype?.bloodType)

This block of code invokes readProfile(), the method you just created and shows the text in the proper labels of the UI.

Interestingly, biologicalSexLiteral and bloodTypeLiteral are not actually HealthKit methods. They’re just two convenience methods — remember I said these were included — that simply return a string based on the numeric value of the blood type and biological sex.

Now you’ve got the app, characteristics and the store talking to one another, so build and run the app.

Go to the Profile & BMI view, and click Read HealthKit Data. You’ll see that the data in the table view shows whatever you entered in the Health App.

Read Characteristics

Awesome! You just read the user characteristics from the HealthKit store.

Querying Samples

Now you’re going to read the user’s weight and height, then show it in the view along with the calculated BMI based on those measurements.

To read data from the store other than characteristics, you need to use a query, specifically, the query base class HKQuery. This is an abstract class with implementations for every type of object. And in order to read samples, you’ll need to create an HKSampleQuery.

To build a query, you need:

  • To specify the type of sample you want to query for (such as weight or height).
  • An optional NSPredicate with the search conditions (such as begin and end date), and an array of NSSortDescriptors that tell the store how to order the samples.

Once you have the query, you just call the HKHealthStore method executeQuery() to get the results.

Note: If you’re familiar with Core Data, you probably noticed some similarities. An HKSampleQuery is very similar to an NSFetchedRequest for an entity type, where you specify the predicate and sort descriptors, and then ask the Object context to execute the query to get the results.

Your query should start with a generic method that reads the most recent sample of any type. This includes height and weight, as you’ll want to present the most recent measurements for these.

Open HealthManager.swift and add this method within the HealthManager class:

func readMostRecentSample(sampleType:HKSampleType , completion: ((HKSample!, NSError!) -> Void)!)
{
 
  // 1. Build the Predicate
  let past = NSDate.distantPast() as NSDate
  let now   = NSDate()
  let mostRecentPredicate = HKQuery.predicateForSamplesWithStartDate(past, endDate:now, options: .None)
 
  // 2. Build the sort descriptor to return the samples in descending order
  let sortDescriptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate, ascending: false)
  // 3. we want to limit the number of samples returned by the query to just 1 (the most recent)
  let limit = 1
 
  // 4. Build samples query
  let sampleQuery = HKSampleQuery(sampleType: sampleType, predicate: mostRecentPredicate, limit: limit, sortDescriptors: [sortDescriptor])
    { (sampleQuery, results, error ) -> Void in
 
      if let queryError = error {
        completion(nil,error)
        return;
      }
 
      // Get the first sample
      let mostRecentSample = results.first as? HKQuantitySample
 
      // Execute the completion closure
      if completion != nil {
        completion(mostRecentSample,nil)
      }
  }
  // 5. Execute the Query
  self.healthKitStore.executeQuery(sampleQuery)
}

To get the most recent sample, you built a query for samples ordered by date in descending order. In this case, the most recent will be the first one returned by the query.

Because you only need the first (most recent) one, you use limit to limit the number of samples returned to 1. This saves time and resources compared to returning the entire set and reducing from there.

Dive deeper into the inner workings of the query:

  1. This builds a predicate based on the date interval by using predicateForSamplesWithStartDate(_:endDate:options). Note: You’re filtering by date here for demonstration purposes – it isn’t actually required in this case and could have been set to nil.
  2. Creates the sort descriptor to return samples ordered by start date, in descending order
  3. Since you want just the latest sample, the query limit is set to 1.
  4. Builds the query object, using a passed in sample type, the predicate, the sample limit and the sort descriptor. When the query finishes, it’ll call the completion closure, where you get the first sample and call your own completion closure with the read value.
  5. Finally, the query executes.

Now you need to call this method in the UI. Open ProfileViewController.swift and add the following property definitions to ProfileViewController:

var height, weight:HKQuantitySample?

You’ll use these two HKQuantitySample properties to read the weight and height samples from the HealthStore.

Now, locate updateWeight and replace this line:

println("TODO: update Weight")

With this code:

// 1. Construct an HKSampleType for weight
let sampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass)
 
// 2. Call the method to read the most recent weight sample
self.healthManager?.readMostRecentSample(sampleType, completion: { (mostRecentWeight, error) -> Void in
 
  if( error != nil )
  {
    println("Error reading weight from HealthKit Store: \(error.localizedDescription)")
    return;
  }
 
  var weightLocalizedString = self.kUnknownString;
  // 3. Format the weight to display it on the screen
  self.weight = mostRecentWeight as? HKQuantitySample;
  if let kilograms = self.weight?.quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo)) {
    let weightFormatter = NSMassFormatter()
    weightFormatter.forPersonMassUse = true;
    weightLocalizedString = weightFormatter.stringFromKilograms(kilograms)
  }
 
  // 4. Update UI in the main thread
  dispatch_async(dispatch_get_main_queue(), { () -> Void in
    self.weightLabel.text = weightLocalizedString
    self.updateBMI()
 
  });
});

Let’s go over this section by section:

  1. First, you specify the type of quantity sample that you want to read by using quantityTypeForIdentifier (from HKSample) and you pass the identifier associated to the weight type: HKQuantityTypeIdentifierBodyMass.
  2. Then, use that sample type to call the method you just created in the HealthManager, so that the method returns samples for weight.
  3. In the completion closure, get the weight sample value in kilograms using doubleValueForUnit and then use an NSMassFormatter to transform that value into a localized text string.
  4. Updates the UI on the main thread to display the weight. HealthKit uses an internal thread, so it’s important to make sure that all UI updates happen on the main thread. You also call a method named updateBMI — this is one of those that’s included in the starter project to calculate and display Body Mass Index (more on this later).

What is this new NSMassFormater?

You introduced this new class in your latest addition to the code, and although it’s not part of HealthKit, but is closely related. iOS 8 brought this and other formatters like NSLengthFormatter and NSEnergyFormatter into the picture. They convert quantities into text strings and take the user’s location into account.

When you use them, you free yourself from localizing the strings or configuring the units of the current locale. The formatters take care of details like these.

For example, say you’re using kilograms; even if your system is not configured to use the metric system the formatter automatically converts it to the proper unit.

Now, you need to do the same for the height. Locate the method updateHeight() and replace the line:

println("TODO: update Height")

with:

// 1. Construct an HKSampleType for Height
let sampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)
 
// 2. Call the method to read the most recent Height sample
self.healthManager?.readMostRecentSample(sampleType, completion: { (mostRecentHeight, error) -> Void in
 
  if( error != nil )
  {
    println("Error reading height from HealthKit Store: \(error.localizedDescription)")
    return;
  }
 
  var heightLocalizedString = self.kUnknownString;
  self.height = mostRecentHeight as? HKQuantitySample;
  // 3. Format the height to display it on the screen
  if let meters = self.height?.quantity.doubleValueForUnit(HKUnit.meterUnit()) {
    let heightFormatter = NSLengthFormatter()
    heightFormatter.forPersonHeightUse = true;
    heightLocalizedString = heightFormatter.stringFromMeters(meters);
  }
 
 
  // 4. Update UI. HealthKit use an internal queue. We make sure that we interact with the UI in the main thread
  dispatch_async(dispatch_get_main_queue(), { () -> Void in
    self.heightLabel.text = heightLocalizedString
    self.updateBMI()
  });
})

As you can see, this piece of code is almost identical to the code that reads the weight, but with two notable differences.

  1. First, the height sample type is constructed with the associated height type identifier HKQuantityTypeIdentifierHeight to allow you to read those samples.
  2. Second, it’s using an NSLengthFormatter to get a localized string from the height value, which is used to get localized strings for lengths.

Now you’ll use the weight and height you just read from the HealthKit store to calculate the BMI (Body Mass Index) and show it on the screen. Open ProfileViewController.swift and locate updateBMI().

Replace the line:

println("TODO: update BMI")

with:

if weight != nil && height != nil {
  // 1. Get the weight and height values from the samples read from HealthKit
  let weightInKilograms = weight!.quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo))
  let heightInMeters = height!.quantity.doubleValueForUnit(HKUnit.meterUnit())
  // 2. Call the method to calculate the BMI
  bmi  = calculateBMIWithWeightInKilograms(weightInKilograms, heightInMeters: heightInMeters)
}
// 3. Show the calculated BMI
var bmiString = kUnknownString
if bmi != nil {
  bmiLabel.text =  NSString(format: "%.02f", bmi!)
}

What this code does, in detail:

  1. Retrieves the double values for weight and height with the sample method doubleValueForUnits(); this is also where you specify the unit you want.

    Note: HKUnit provides methods to construct all the supported units, and here you’ve used grams for weight and meters for height. You must be careful about using compatible units, because if the requested unit is not compatible with the sample type, it’ll raise an exception. For instance, requesting a weight value using a distance unit wouldn’t work.

  2. The BMI is calculated by calling calculateBMIWithWeightInKilograms(), a utility method included in the starter project that calculates BMI from weight and height.
  3. Shows the BMI value in the corresponding label. Since BMI is just a number, you don’t need any formatter to do that.

Note: You’ll be stuck soon if you’ve not added data in the HealthKit store for the app to read. If you haven’t already, you need to create some height and weight samples at the very least.

Open the Health App, and go to the Health Data Tab. There, select the Body Measurements option, then choose Weight and then Add Data Point to add a new weight sample. Repeat the process for the Height.

Now, build and run the app. Go to the Profile & BMI view, and tap on Read HealthKit Data. If you’ve added weight and height samples in the Health App, you’ll see something like this:

Read Samples and BMI

Awesome! You just read your first samples from the HealthKit store and used them to calculate the BMI.

Saving Samples

In this section, you’ll learn how to save samples to the HealthKit store. Your test subject will be the BMI value you calculated in the previous section.

Open HealthManager.swift and add this method:

func saveBMISample(bmi:Double, date:NSDate ) {
 
  // 1. Create a BMI Sample
  let bmiType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMassIndex)
  let bmiQuantity = HKQuantity(unit: HKUnit.countUnit(), doubleValue: bmi)
  let bmiSample = HKQuantitySample(type: bmiType, quantity: bmiQuantity, startDate: date, endDate: date)
 
  // 2. Save the sample in the store
  healthKitStore.saveObject(bmiSample, withCompletion: { (success, error) -> Void in
    if( error != nil ) {
      println("Error saving BMI sample: \(error.localizedDescription)")
    } else {
      println("BMI sample saved successfully!")
    }
  })
}

Here’s what this code does:

  1. Creates a sample object using HKQuantitySample. In order to create a sample, you need:
    • A Quantity type object, likeHKQuantityType, initialized using the proper sample type. In this case, HKQuantityTypeIdentifierBodyMassIndex).
    • A Quantity object, likeHKQuantity, initialized using the passed in bmi value and the unit. In this case, since BMI is a scalar value with no units, you need to use a countUnit.
    • Start and end date, which in this case is the current date and time in both cases.
  2. HKHealthStore‘s method saveObject() is called to add the sample to the HealthKit store.

Now you’ll need to use this method in the view controller to save the BMI sample. Open ProfileViewController.swift and locate saveBMI().

Replace the line:

println("TODO: save BMI sample")

With:

// Save BMI value with current BMI value
if bmi != nil {
  healthManager?.saveBMISample(bmi!, date: NSDate())
}
else {
  println("There is no BMI data to save")
}

Quite simple – it invokes the method you just created with the BMI value and current date.

Build and run. Navigate to the Profile view and tap Read HealthKit Data to read the information, calculate the BMI and display the results. Next, tap Save BMI to save the calculated value. If everything worked, you’ll see this message in Xcode’s console:

BMI sample saved successfully!

Nicely done. Sample saved. You can verify if it’s really on the HealthKit store using the Health App. Open the Health App, and navigate to the Health Data tab. Then go to Body Measurements and then Body Mass Index.

BMI Sample in Health App

If you see something like that, you did well. It means the BMI you calculated is there, ready for the user the check it with the Health app or other third party apps!

Where To Go From Here?

Here is the example project as it stands at this point.

Important!: The completed project requires modification before you can use it with HealthKit, as it’s set to a sample bundleID. You’ll need to set it up with a bundle ID of your own, select your Team, and then turn HealthKit OFF and ON under Capabilities of the target.

See the sections on Getting Started and Entitlements and Permissions above for more detailed instructions.

Congratulations, you’ve got some hands-on experience with HealthKit! You now know how to request permissions, read characteristics, and read and write samples.

If you want to learn more, stay tuned for the next part of this HealthKit tutorial series where you’ll learn more about a more complex type of data: workouts.

In the meantime, if you have any questions or comments, please join the forum discussion below!

HealthKit Tutorial with Swift: Getting Started is a post from: Ray Wenderlich

The post HealthKit Tutorial with Swift: Getting Started appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4370

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>