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
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Adding Samples to Workouts

Up to this point, you have assumed that a Prancercise workout is composed of a single workout session. But if you find Prancercise a little too intense, you might want to break it into a few short intervals.

With samples, you can record many exercise intervals under the same workout. It’s a way to give HealthKit a more detailed view of what you did during your workout routine.

You can add all kinds of samples to a workout. If you want, you can add distance, calories burned, heart rate and more.

Because Prancercise is more of a dance routine, this HealthKit tutorial will focus on calorie burn samples.

Making Model Updates

This is a new way of thinking about Prancercise workouts, and that means it’s time to change the model.

Instead of using a single Prancercise workout model, you need the concept of a workout interval representing a short session. That way, a single PrancerciseWorkout becomes a wrapper or container for the workout intervals that store the starts and stops you took during your routine.

Open Workout.swift. Rename PrancerciseWorkout to PrancerciseWorkoutInterval so it looks like this:

struct PrancerciseWorkoutInterval {
  var start: Date
  var end: Date
  
  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
  }
}

You can see that nothing else has changed. What was once an entire workout is now a piece of a workout.

Paste the following code beneath the struct declaration for PrancerciseWorkoutInterval:

struct PrancerciseWorkout {
  var start: Date
  var end: Date
  var intervals: [PrancerciseWorkoutInterval]
  
  init(with intervals: [PrancerciseWorkoutInterval]) {
    self.start = intervals.first!.start
    self.end = intervals.last!.end
    self.intervals = intervals
  }
}

Now, a full PrancerciseWorkout is composed of an array of PrancerciseWorkoutInterval values. The workout starts when the first item in the array starts, and it ends when the last item in the array ends.

This is a nice way of representing a workout as something composed of time intervals, but it’s still missing a concept of duration and total energy burned. The code won’t compile until you have defined those.

Functional programming comes to the rescue here. You can use reduce to sum up the duration and total energy burned properties on PrancerciseWorkoutInterval.

Paste the following computed properties below init(with:) in PrancerciseWorkout:

var totalEnergyBurned: Double {
  return intervals.reduce(0) { (result, interval) in
    result + interval.totalEnergyBurned
  }
}

var duration: TimeInterval {
  return intervals.reduce(0) { (result, interval) in
    result + interval.duration
  }
}

reduce takes a starting value — in this case zero — and a closure that takes in the result of the previous computation. It does this for each item in the array.

To calculate the total energy burned, reduce starts at zero and adds zero to the first energy burn value in the array. Then it takes the result and adds it to the next value in the array, and so on. Once it has hit the end of the array, you get a sum total of all energy burned throughout your workout.

Reduce is a handy function for summing up arrays. If you would like to read more about functional programming and its awesomeness, check out this article.

Workout Sessions

You are almost finished upgrading the models. Open WorkoutSession.swift and take a look.

WorkoutSession is used to store data related to the current PrancerciseWorkout being tracked. Since you added in this concept of workout intervals to PrancerciseWorkout, WorkoutSession needs to add new intervals each time you start and end a Prancercise session.

Inside of the WorkoutSession class, locate the line that declares the state variable. It looks like this:

var state: WorkoutSessionState = .notStarted

Below it, add a new line that declares an array of PrancerciseWorkoutInterval values:

var intervals: [PrancerciseWorkoutInterval] = []

When you finish a Prancercise session, a new interval will get added to this array. You’ll add a function to do that.

Add the following method below clear() in WorkoutSession:

private func addNewInterval() {
  let interval = PrancerciseWorkoutInterval(start: startDate,
                                            end: endDate)
  intervals.append(interval)
}

This method takes the start and end dates from the workout session, and it creates a PrancerciseWorkoutInterval from them. Note that the start and end dates get reset every time a Prancercise session begins and ends.

Now that you have a way to add workout intervals, replace the end() method in WorkoutSession with this:

func end() {
  endDate = Date()
  addNewInterval()
  state = .finished
}

You can see that, right after you set the end date for the session, you call addNewInterval().

It’s also important to clear out the array whenever the workout session needs to get cleared. So, add this at the end of clear():

intervals.removeAll()

removeAll() does exactly what it says. It removes ALL. :]


There’s one more modification. completeWorkout needs to use the intervals to create a new PrancerciseWorkout object.

Replace the declaration of completeWorkout with this:

var completeWorkout: PrancerciseWorkout? {
  guard state == .finished, intervals.count > 0 else {
    return nil
  }
  
  return PrancerciseWorkout(with: intervals)
}

There you have it. Since this property is optional, you only want it to return a full PrancerciseWorkout when the WorkoutSession is finished and you have recorded at least one interval.

Every time you stop the workout, a new PrancerciseWorkoutInterval with the current start and stop dates gets added to the list. Once the user taps Done to save the workout, this code generates a full-blown PrancerciseWorkout entity using the intervals recorded during the many sessions.

There’s no need to change the code in CreateWorkoutViewController. The button actions call the same start(), end(), and clear() methods. The only difference is that instead of working with a single time interval, WorkoutSession generates and stores many.

Build and run. Play around with starting and stopping your Prancercise workout, then tap Done to add your workout to the list.

One thing to note is that, although the app records accurate Prancercise workouts to HealthKit, there aren’t any samples attached. You need a way to convert PrancerciseWorkoutIntervals into samples.