HealthKit Tutorial With Swift: Workouts

This HealthKit tutorial shows you step by step how to track workouts using the HealthKit APIs by integrating an app with the system’s Health app. By Felipe Laso-Marsetti.

4.1 (7) · 2 Reviews

Download materials
Save for later
Share
Update note: Felipe Laso-Marsetti updated this tutorial for Swift 4.2, Xcode 10 and iOS 12. Ernesto García wrote the original.

Welcome back to our HealthKit tutorial series!

In the first part of the series, you learned the basics of working with HealthKit: reading and writing data.

In this second and final part of the series, you’ll learn how to work with a more complex type of data: workouts.

This project picks up where the previous HealthKit tutorial left off. If you don’t have the project already, then download the materials for this tutorial using the Download materials button at the top or bottom of the tutorial.

Get ready to take another rep in your HealthKit workout! :]

Getting Started

In your day-to-day life, a workout is a simple thing. It’s some period of time in which you increase physical exertion doing some sort of activity.

Most workouts have one more of the following attributes:

  • Activity type (running, cycling, Prancercising, etc.)
  • Distance
  • Start and end time
  • Duration
  • Energy burned

HealthKit thinks of workouts in much the same way. A workout is a container for these types of information, taken as a collection of samples. A given workout might contain heart rate samples, distance samples and an activity type to categorize them.

Continuing from the previous HealthKit tutorial, you’re going to track a special kind of workout: Prancercise.

The starter project already contains a view controller that gives you a place to track your Prancercise workouts. To see it, navigate to Prancercise Workouts and then tap the + button.

HealthKit tutorial

This view contains a button that starts a Prancercise workout. Tap the button once. The app will start to track your Prancercise session, showing you the start time and duration.

HealthKit tutorial

Tap the button a second time. You’ll see that the current Prancercise session will stop. At this point, you can tap Done to record the workout, or you can tap the New Prancercise button to start a new workout session (be aware this erases the old session).

HealthKit tutorial

Saving Workouts

For the moment, the app doesn’t actually do anything when you tap Done to save your workout. You’re going to change that.

First, a little bit of context: Open Workout.swift and take a look around. You should see a struct named PrancerciseWorkout.

struct PrancerciseWorkout {
  var start: Date
  var end: Date
  
  init(start: Date, end: Date) {
    self.start = start
    self.end = end
  }
  
  var duration: TimeInterval {
    return end.timeIntervalSince(start)
  }
  
  var totalEnergyBurned: Double {
    let prancerciseCaloriesPerHour: Double = 450
    let hours: Double = duration / 3600
    let totalCalories = prancerciseCaloriesPerHour * hours
    return totalCalories
  }
}

PrancerciseWorkout is the model the app uses to store information related to a workout. The app creates one every time you tap the Done button after finishing your Prancercise session.

Each PrancerciseWorkout keeps track of its:

  1. Start and end time
  2. Duration
  3. Total calories burned

Your app feeds these values into HealthKit when you save your workout.

Note: Here, you’re assuming a somewhat intense Prancercise pace with aggressive ankle weights and loud fist-pumping musical accompaniment. Hence, the workout burns 450 calories per hour. Eat it, Zumba!

Now that you understand what a PrancerciseWorkout contains, you’ll save one.

Open WorkoutDataStore.swift and take a look at save(prancerciseWorkout:completion:). This is what you’ll use to save your Prancercise workout to HealthKit.

Add the following lines of code into that method:

let healthStore = HKHealthStore()
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .other

The first thing you do is create a health store and workout configuration to store data into HealthKit. Next, add the following to the end of the method:

let builder = HKWorkoutBuilder(healthStore: healthStore,
                               configuration: workoutConfiguration,
                               device: .local())
    
builder.beginCollection(withStart: prancerciseWorkout.start) { (success, error) in
  guard success else {
    completion(false, error)
    return
  }
}

Use the HKWorkoutBuilder class (new to iOS 12) to incrementally construct a workout and begin collecting workout data. Next, add the following just before the closing brace of `beginCollection(withStart:completion:)`’s completion handler to get the number of calories burned:

guard let quantityType = HKQuantityType.quantityType(
  forIdentifier: .activeEnergyBurned) else {
  completion(false, nil)
  return
}
    
let unit = HKUnit.kilocalorie()
let totalEnergyBurned = prancerciseWorkout.totalEnergyBurned
let quantity = HKQuantity(unit: unit,
                          doubleValue: totalEnergyBurned)

You may remember HKQuantity from the earlier HealthKit tutorial. You used it to read and write your user’s height, weight, and body mass index. Here, you’re creating a quantity to store the total calories burned in this sample. After that, you can create a new sample. Add this code immediately following the code you just added:

let sample = HKCumulativeQuantitySeriesSample(type: quantityType,
                                              quantity: quantity,
                                              start: prancerciseWorkout.start,
                                              end: prancerciseWorkout.end)

HKCumulativeQuantitySeriesSample is a new sample type that stores the total data for a workout rather than individual values. You can use this, for example, to collect the total distance you ran in a basketball game by adding up all the individual samples. Finally, add the sample to the builder by adding the following code to the method:

//1. Add the sample to the workout builder
builder.add([sample]) { (success, error) in
  guard success else {
    completion(false, error)
    return
  }
      
  //2. Finish collection workout data and set the workout end date
  builder.endCollection(withEnd: prancerciseWorkout.end) { (success, error) in
    guard success else {
      completion(false, error)
      return
    }
        
    //3. Create the workout with the samples added
    builder.finishWorkout { (_, error) in
      let success = error == nil
      completion(success, error)
    }
  }
}

Here’s what’s going on in the code above:

  1. With the sample ready, it’s time to add it to the workout that you’re building.
  2. Here, you finish the collection of workout data and set the end date for the workout.
  3. You create the workout with all the samples that have been added. Note how you’ve been nesting calls within completion blocks. This is to ensure that no race conditions occur and that each step is finished before attempting the next. If you’re not seeing the right data in your apps compared to HealthKit, it may be due to the workout builder’s more block-based, asynchronous nature.

Quite fittingly, a Prancercise Workout is an activity that can only be categorized as Other, but you can pick from any number of supported activity types if you like. :]

You may have also noticed that you can tell HealthKit (when creating the HKWorkoutBuilder) what device the workout was recorded on. This can be useful when querying data later.