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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
HealthKit Tutorial With Swift: Workouts
30 mins
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 PrancerciseWorkoutInterval
s into samples.