A workout sample stores information about a single physical activity.
SDKs
- iOS 8.0+
- Mac Catalyst 13.0+
- watchOS 2.0+
Framework
- Health
Kit
Declaration
class HKWorkout : HKSample
Overview
The HKWorkout
class is a concrete subclass of the HKSample
class. The workout records summary about a single physical activity (for example, the duration, total distance, and total energy burned). It also acts as a container for other HKSample
objects. You can associate any number of samples with a workout, letting you add fine-grain tracking over the course of the workout. For example, you may want to break a single run into a number of shorter intervals, and then add samples to track the user’s heart rate, energy burned, distance traveled, and steps taken for each interval.
Note
If a workout has summary information, it also needs a set of associated samples that add up to the summary’s total.
HealthKit supports a wide range of activity types. For a complete list, see HKWorkout
.
Workouts are mostly immutable. You set their properties when you instantiate the workout, and they cannot change. However, you can continue to add samples to the workouts.
Adding Samples to a Workout
You can associate samples with a workout using the HealthKit store’s add(_:
method. The workout must be saved to the HealthKit store before you can add any samples. The samples don’t need to be saved. Adding them to the workout automatically saves them (if they have not been saved already). The following sample shows how to associate energy burned and heart rate samples with a workout.
// This sample uses hard-coded values and performs all the operations inline
// for simplicity's sake. A real-world app would calculate these values
// from sensor data and break the operation up using helper methods.
let energyBurned = HKQuantity(unit: HKUnit.kilocalorieUnit(),
doubleValue: 425.0)
let distance = HKQuantity(unit: HKUnit.mileUnit(),
doubleValue: 3.2)
// Provide summary information when creating the workout.
let run = HKWorkout(activityType: HKWorkoutActivityType.Running,
startDate: start, endDate: end, duration: 0,
totalEnergyBurned: energyBurned, totalDistance: distance, metadata: nil)
// Save the workout before adding detailed samples.
healthStore.saveObject(run) { (success, error) -> Void in
guard success else {
// Perform proper error handling here...
fatalError("*** An error occurred while saving the " +
"workout: \(error?.localizedDescription)")
}
// Add optional, detailed information for each time interval
var samples: [HKQuantitySample] = []
guard let distanceType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning) else {
fatalError("*** Unable to create a distance type ***")
}
let distancePerInterval = HKQuantity(unit: HKUnit.footUnit(),
doubleValue: 165.0)
let distancePerIntervalSample =
HKQuantitySample(type: distanceType, quantity: distancePerInterval,
startDate: intervals[0], endDate: intervals[1])
samples.append(distancePerIntervalSample)
guard let energyBurnedType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned) else {
fatalError("*** Unable to create an energy burned type ***")
}
let energyBurnedPerInterval = HKQuantity(unit: HKUnit.kilocalorieUnit(),
doubleValue: 15.5)
let energyBurnedPerIntervalSample =
HKQuantitySample(type: energyBurnedType, quantity: energyBurnedPerInterval,
startDate: intervals[0], endDate: intervals[1])
samples.append(energyBurnedPerIntervalSample)
guard let heartRateType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate) else {
fatalError("*** Unable to create a heart rate type ***")
}
let heartRateForInterval = HKQuantity(unit: HKUnit(fromString: "count/min"),
doubleValue: 95.0)
let heartRateForIntervalSample =
HKQuantitySample(type: heartRateType, quantity: heartRateForInterval,
startDate: intervals[0], endDate: intervals[1])
samples.append(heartRateForIntervalSample)
// Continue adding detailed samples...
// Add all the samples to the workout.
self.healthStore.addSamples(samples,
toWorkout: run) { (success, error) -> Void in
guard success else {
// Perform proper error handling here...
fatalError("*** An error occurred while adding a " +
"sample to the workout: \(error?.localizedDescription)")
}
}
}
You can create a query that returns only the samples associated with a workout by adding the workout to the query’s predicate. The predicate
method creates a predicate object that matches only samples associated with the provided workout.
guard let distanceType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning) else {
fatalError("*** Unable to create the distance type ***")
}
let workoutPredicate =
HKQuery.predicateForObjectsFromWorkout(workout)
let startDateSort =
NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)
let query = HKSampleQuery(sampleType: distanceType, predicate: workoutPredicate,
limit: 0, sortDescriptors: [startDateSort]) {
(sampleQuery, results, error) -> Void in
guard let distanceSamples = results as? [HKQuantitySample] else {
// Perform proper error handling here...
fatalError("*** An error occurred while adding a sample to " +
"the workout: \(error?.localizedDescription)")
}
// process the detailed samples...
}
healthStore.executeQuery(query)
Associating samples with a workout provides fine-grain information about the workout. However, adding samples to the workout does not change any of the workout’s properties. Specifically, adding distance samples won’t change the quantity stored in the total
, start
, end
or duration
properties. Likewise, adding active energy burned samples won’t change the quantity stored in the total
property. This can lead to some duplication in data between the workout’s properties and the associated samples.
Your app should always provide data for the workout’s duration
, total
, and total
properties when the data is both available and relevant to the workout. In addition, you should provide a set of associated samples that sum up to these totals. You can also provide additional associated samples, to help track how the intensity of the exercise changed during the course of the workout.
For example, imagine an app that tracks a user’s runs. Whenever a user finishes a run, the app creates a workout that includes the total distance, duration, and calories burned for the entire run. The app also saves samples that describe the distance, calories burned, step count, heart rate, flights climbed, and other data over much smaller time intervals.
You need to fine tune the exact length of your associated samples based on the type of workout and the needs of your app. Using five-minute intervals minimizes the amount of memory needed to store the workouts, while still providing a general sense of the change in intensity over the course of a long workout. Using five-second intervals provides a much-more detailed view of the workout, but requires considerably more memory and processing.
Using Workouts
Like many HealthKit classes, the HKWorkout
class should not be subclassed.
You may extend workouts by adding metadata with custom keys as appropriate for your app. For more information, see the methods init(activity
and init(activity
.