HealthKit Tutorial with Swift: Workouts

Ernesto García

Learn about the new HealthKit API in iOS 8!

Update 4/19/2015: Updated for Xcode 6.3 / Swift 1.2

Update 12/8/14: Updated for Xcode 6.1.1.

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 deal with a more complex type of data: workout data.

This project picks up where the previous tutorial left off. If you don’t have the project already, you can download it here.

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

Getting Started

In the physical realm, a workout consists of increased physical activity during a period of time. In both the digital and physical realms, you can create a workout around the following basic attributes:

  • Activity type e.g. running, cycling, curling
  • Distance
  • Start and end dates
  • Duration
  • Energy burned during the workout

In the digital realm that is HealthKit, a workout is a container for other types of samples. For instance, you could add a group of samples of your heart rate during your workout. It’s a powerful feature if you plan to work on a fitness app.

For this project, you’re going to store running workouts, but you can easily change the activity type to represent any other workout.

The starter project already contains a view controller that gives you a place to enter workout information. To see it, navigate to Workouts and then tap the + button.

NewWorkout

This view collects information and returns it to the Workouts view controller when dismissed. You’ll need to use that information to create a workout.

Saving Workouts

First, you’re going to create a method to save a running workout. Open HealthManager.swift and add this method:

func saveRunningWorkout(startDate:NSDate , endDate:NSDate , distance:Double, distanceUnit:HKUnit , kiloCalories:Double,
  completion: ( (Bool, NSError!) -> Void)!) {

    // 1. Create quantities for the distance and energy burned
    let distanceQuantity = HKQuantity(unit: distanceUnit, doubleValue: distance)
    let caloriesQuantity = HKQuantity(unit: HKUnit.kilocalorieUnit(), doubleValue: kiloCalories)

    // 2. Save Running Workout
    let workout = HKWorkout(activityType: HKWorkoutActivityType.Running, startDate: startDate, endDate: endDate, duration: abs(endDate.timeIntervalSinceDate(startDate)), totalEnergyBurned: caloriesQuantity, totalDistance: distanceQuantity, metadata: nil)
    healthKitStore.saveObject(workout, withCompletion: { (success, error) -> Void in
      if( error != nil  ) {
        // Error saving the workout
        completion(success,error)
      }
      else {
        // Workout saved
        completion(success,nil)

      }
    })
}

And what does this code do? Line by line, here’s the scoop:

  1. Creates the quantity objects associated to the distance and energy in the same way you created the quantity object for the BMI –using a double value and the proper unit type.
  2. An HKWorkout object is created with duration, start and end dates and the quantities you just created for energy burned and distance. Then, the workout is added to the store by invoking the saveObject method of the HealthKit Store. The result and error return during the completion closure.

Now you need to call this method in the Workouts View Controller. Open WorkoutsTableViewController.swift and locate the method unwindToSegue(). This method is invoked when you select Done on the New Workout view.

Replace the line:

println("TODO: Save workout in Health Store")

with:

if let addViewController:AddWorkoutTableViewController = segue.sourceViewController as? AddWorkoutTableViewController {

// 1. Set the Unit type
var hkUnit = HKUnit.meterUnitWithMetricPrefix(.Kilo)
if distanceUnit == .Miles {
  hkUnit = HKUnit.mileUnit()
}

// 2. Save the workout
self.healthManager?.saveRunningWorkout(addViewController.startDate!, endDate: addViewController.endDate!, distance: addViewController.distance , distanceUnit:hkUnit, kiloCalories: addViewController.energyBurned!, completion: { (success, error ) -> Void in
  if( success )
  {
    println("Workout saved!")
  }
  else if( error != nil ) {
    println("\(error)")
  }
})
}
  1. First, it creates the proper unit object. In this app, the user chooses the distance unit type from a segmented control which sets the distanceUnit property. This code checks distanceUnit to determine the proper HKUnit to use.
  2. After creating the unit, it calls the saveRunningWorkoutMethod() to store the workout with the start date, end date, duration and energy burned.

Build and run. Tap on the + button and fill in the data in the view like this:

New Workout

Wow! 26.2 miles (42.195 km) in 2 hours and 1 minute. I think you just beat the marathon world record while coding. You’re pretty talented!

Tap Done when you’ve finished. If everything works fine, you’ll see this message in Xcode console:

Workout saved!

Great! Your workout saved successfully to the HealthKit store. You can repeat this operation to add more workouts if you want.

Now it’s time to display the workouts in the table view!

Querying Workouts

If you run the app and open the Workouts View Controller, you won’t see the workouts you’ve created in the view.

You need to add the code to read and display them, and in order to read the workouts you need to create a HKSampleQuery and execute it to retrieve the data.

This will be very similar to the code used to read weight and height, so why not try writing it yourself?

Create a method in HealthManager.swift that queries for workouts of type HKWorkoutActivityType, orders them by start date descending and returns the results in a completion block. Use the following method signature:

func readRunningWorkOuts(completion: (([AnyObject]!, NSError!) -> Void)!)
Solution Inside: readRunningWorkOuts Implementation SelectShow

You need to show the workouts in the table, so you’ll call this method and implement the table view data source next, so open WorkoutsTableViewController.swift.

You need to create an array property in this class to store the workouts. Add this code near the other property definitions at the top of WorkoutsTableViewController:

var workouts = [HKWorkout]()

Then, add this method, which will read the workouts when the view appears:

public override func viewWillAppear(animated: Bool) {
  super.viewWillAppear(animated)

  healthManager?.readRunningWorkOuts({ (results, error) -> Void in
    if( error != nil )
    {
      println("Error reading workouts: \(error.localizedDescription)")
      return;
    }
    else
    {
      println("Workouts read successfully!")
    }

    //Kkeep workouts and refresh tableview in main thread
    self.workouts = results as! [HKWorkout]
    dispatch_async(dispatch_get_main_queue(), { () -> Void in
      self.tableView.reloadData()
    });

  })
}

This invokes readWorkouts, a method you’ve created. When the results are received, it stores them in workouts and then reloads the table view data on the main thread.

Now you need to add the table view to the datasource’s methods, so add this implementation of tableView:numberOfRowsInSection in WorkoutsTableViewController:

public override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return  workouts.count
}

This is pretty straightforward. When the table view asks for the number of rows, you just return the number of workouts you’ve read from the store.

Now it’s time to populate the table cells. You need to implement the tableView:cellForRowAtIndexPath method of the table view data source. Add this code to the WorkoutsTableViewController class:

public override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("workoutcellid", forIndexPath: indexPath) as! UITableViewCell


  // 1. Get workout for the row. Cell text: Workout Date
  let workout  = workouts[indexPath.row]
  let startDate = dateFormatter.stringFromDate(workout.startDate)
  cell.textLabel!.text = startDate

  // 2. Detail text: Duration - Distance 
  // Duration
  var detailText = "Duration: " + durationFormatter.stringFromTimeInterval(workout.duration)!
  // Distance in Km or miles depending on user selection
  detailText += " Distance: "
  if distanceUnit == .Kilometers {
    let distanceInKM = workout.totalDistance.doubleValueForUnit(HKUnit.meterUnitWithMetricPrefix(HKMetricPrefix.Kilo))
    detailText += distanceFormatter.stringFromValue(distanceInKM, unit: NSLengthFormatterUnit.Kilometer)
  }
  else {
    let distanceInMiles = workout.totalDistance.doubleValueForUnit(HKUnit.mileUnit())
    detailText += distanceFormatter.stringFromValue(distanceInMiles, unit: NSLengthFormatterUnit.Mile)

  }
  // 3. Detail text: Energy Burned 
  let energyBurned = workout.totalEnergyBurned.doubleValueForUnit(HKUnit.jouleUnit())
  detailText += " Energy: " + energyFormatter.stringFromJoules(energyBurned)
  cell.detailTextLabel?.text = detailText;


  return cell
}

An explanation of the above:

  1. This gets the workout for that row and formats the startDate to show it in the cell’s text label. In order to format the date, it uses an NSDateFormatter that was pre-created for you in the starter project.
  2. Prepares a detail label string with the distance and the energyBurned. The distance displays in miles or kilometers based on the user selection (stored in the property distanceUnit). It gets the doubleValue for the distance, and passes a distance unit based on the value of that property. Then, it uses an NSDistanceFormatter to get the localized distance string by invoking the method stringFromValue:unit with the proper unit. For the duration of the workout, an NSDateComponentsFormatter is used. All these formatters are pre-created for you in the starter project.
  3. Adds the energy burned to the string using an NSEnergyFormatter. The string is finally displayed in the detailTextLabel.

Build and run the app. Navigate to Workouts, and now you should see the workouts you stored earlier in the table view:

Read workouts

Cool, you’ve got the workouts showing up as expected. Now tap on the segmented control and see how the distance shows in miles or kilometers, based on your selection.

If you go to the Health App, you won’t find this information anywhere. This is by design. The Health App only shows samples, not workouts.

However, you can let the user see information about the workouts, you’ll just need to associate some samples with them. A health-tracking app without workouts is like a trainer without a jarring scream, so you should link up the samples.

Adding Samples to Workouts

As a final step, you’ll add distance and energy burned samples to the workout.

Open HealthManager.swift and go to saveRunningWorkout(). In the return successful closure, replace these two lines:

// Workout saved
completion(success,nil)

With this :

// if success, then save the associated samples so that they appear in the Health Store
let distanceSample = HKQuantitySample(type: HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning), quantity: distanceQuantity, startDate: startDate, endDate: endDate)
let caloriesSample = HKQuantitySample(type: HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned), quantity: caloriesQuantity, startDate: startDate, endDate: endDate)

self.healthKitStore.addSamples([distanceSample,caloriesSample], toWorkout: workout, completion: { (success, error ) -> Void in
  completion(success, error)
})

At this point, you’re probably very familiar with this code, perhaps even to the point where you feel like you’re repeating yourself. But for the sake of clarity, here’s an explanation.

In the first step, it creates a quantity sample of type DistanceWalkingRunning for the running distance, and another quantity sample of type ActiveEnergyBurned for the calories consumed. Then the HealthKit store method addsamples:ToWorkout is called to add those samples to the workout.

Build and run the app, and add one or more workouts and close the app. Now that those workouts have associated samples for the distance and energy burned, you’ll be able to see them in the Health app.

Open the Health App, and go to the Health Data Tab. There, select the Fitness options. Then choose Walking+Running Distance or Active Calories to check that the data is there. You’ll see something like this:

Workouts data

Great! Now you have the ability to get important information about workouts into the HealthKit store.

Where To Go From Here?

Here you can download the project that contains all the code from this tutorial.

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 in part 1 of the series for more detailed instructions.

Hopefully this tutorial has given you some insight into the basic concepts of HealthKit and how to use them in your own apps. In order to know more about HealthKit, these are the most relevant sources:

After going through those documents and videos, you’ll be ready to dig into some more advanced aspects on HealthKit and add improvements to this app. For instance, you could add new types of samples or workouts, calculate Statistics using HKStatisticsQuery, or observe changes in the store information with HKObserverQuery.

I hope you enjoyed this tutorial, and as always if you have any questions or comments please join the forum discussion below!

Ernesto García

Ernesto is a Mac and iOS developer from Spain. After 16+ years of experience developing Enterprise applications, now he is an indie developer who focuses on creating great mobile and desktop applications. He's the founder of CocoaWithChurros where he develops Apps for clients as well as his own. You can also find him on Twitter or Github.

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

Swift Team

... 15 total!

iOS Team

... 44 total!

Android Team

... 15 total!

macOS Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 13 total!

Resident Authors Team

... 17 total!

Podcast Team

... 3 total!

Recruitment Team

... 9 total!