Article

Running Workout Sessions

Track the user’s activities on Apple Watch.

Overview

Workout sessions let you track a user’s activity on Apple Watch. While the session is running, the system fine tunes the watch’s sensors for the specified activity. For exampe, all workout sessions generate high-frequency heart rate samples; however, an outdoor cycling activity generates more accurate location data, while an indoor cycling activity does not.

To run a workout session:

  1. Start the workout session on Apple Watch.

  2. End the workout session on Apple Watch.

  3. Create and save the workout samples.

Since health data is synced between Apple Watch and iPhone, feel free to create and save the workout samples on the device that makes the most sense for your users. However, If there is a workout in progress, both devices should display current information about the workout.

Starting Workout Sessions

Start a workout session on Apple Watch. Be sure to pick the workout activity type and location that optimizes the accuracy of Apple Watch’s calorimetry, based on the type of workout the user is performing.

HealthKit saves active energy burned samples to the HealthKit store while the workout is running. The calories are calculated based on the activity and location type chosen. HealthKit provides customized calorie calculations for run, walk, cycle, stair climbing, elliptical, and rowing activities. Furthermore, the calculations for run, walk, and cycle activities distinguish between indoor and outdoor locations.

The system calculates the calories for all other activities as “other.” Calories are calculated at a rate no lower than a brisk walk, but may be higher, based on the data from Apple Watch’s sensors.

let configuration = HKWorkoutConfiguration()
configuration.activityType = .running
configuration.locationType = .indoor
 
do {
    let session = try HKWorkoutSession(configuration: configuration)
    
    session.delegate = self
    healthStore.start(session)
}
catch let error as NSError {
    // Perform proper error handling here...
    fatalError("*** Unable to create the workout session: \(error.localizedDescription) ***")
}

Ending the Workout Session

As soon as the workout is over, stop the workout session on Apple Watch. Starting and stopping the workout session ensures that the workout contributes to the Exercise ring. The system updates the exercise ring based on the amount of time the user spent actually exerting themselves during the session, as calculated by the watch’s sensors.

healthStore.endWorkoutSession(session)

Creating and Saving Workout Samples

Create active energy burned samples to represent the calories burned during the workout session. Alternatively, if you want HealthKit to calculate the calories, query for all the active energy burned samples saved by Apple Watch during the workout.

guard let activeEnergyType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned) else {
    fatalError("*** Unable to create the active energy burned type ***")
}
 
let device = HKDevice.localDevice()
 
let datePredicate = HKQuery.predicateForSamplesWithStartDate(workoutSession.startDate, endDate: workoutSession.endDate, options: .None)
let devicePredicate = HKQuery.predicateForObjectsFromDevices([device])
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates:[datePredicate, devicePredicate])
 
let sortByDate = NSSortDescriptor(key:HKSampleSortIdentifierStartDate , ascending: true)
 
let healthStore = self.healthStore
 
let query = HKSampleQuery(sampleType: activeEnergyType, predicate: predicate, limit: Int(HKObjectQueryNoLimit), sortDescriptors: [sortByDate]) { (query, returnedSamples, error) -> Void in
    
    guard let samples = returnedSamples as? [HKQuantitySample] else {
        // Handle the error here.
        print("*** an error occurred: \(error?.localizedDescription) ***")
        return
    }
    
    // Create the workout here.
}
 
healthStore.executeQuery(query)

Next, create an HKWorkout object to represent the workout session. When creating the workout, be sure to do the following:

  • Choose an activity type that best describes the activity that the user actually performed. This is the name that appears in the Activity app.

  • Set the workout object’s total energy to the sum of all the workout’s active energy burned samples.

  • For walk, run, or cycle activity types, use the HKMetadataKeyIndoorWorkout metadata key to properly mark the exercise as either indoor or outdoor.

let energyUnit = HKUnit.kilocalorieUnit()
var totalActiveEnergy : Double = 0.0
 
for sample in samples {
    totalActiveEnergy += sample.quantity.doubleValueForUnit(energyUnit)
}
 
let startDate = workoutSession.startDate ?? NSDate()
let endDate = workoutSession.endDate ?? NSDate()
let duration = endDate.timeIntervalSinceDate(startDate)
 
let totalActiveEnergyQuantity = HKQuantity(unit: energyUnit, doubleValue: totalActiveEnergy)
 
let workout = HKWorkout(activityType: workoutSession.activityType,
                        startDate: startDate,
                        endDate: endDate,
                        duration: duration,
                        totalEnergyBurned: totalActiveEnergyQuantity,
                        totalDistance: nil,
                        device: HKDevice.localDevice(),
                        metadata: [HKMetadataKeyIndoorWorkout : true])
 
guard healthStore.authorizationStatusForType(HKObjectType.workoutType()) == .SharingAuthorized else {
    print("*** the app does not have permission to save workout samples ***")
    return
}
 
healthStore.saveObject(workout, withCompletion: { (success, error) -> Void in
    guard success else {
        // Add proper error handling here.
        print("*** an error occurred: \(error?.localizedDescription) ***")
        return
    }
    
    // Associate active energy burned samples with the workout.
})

Finally, associate the active energy burned samples with the workout. You may also want to create and associate other samples with the workout.

guard healthStore.authorizationStatusForType(activeEnergyType) == .SharingAuthorized else {
    print("*** the app does not have permission to save active energy burned samples ***")
    return
}
 
healthStore.addSamples(mySamples, toWorkout: workout, completion: { (success, error) -> Void in
    guard success else {
        // Handle the error here.
        print("*** an error occurred: \(error?.localizedDescription) ***")
        return
    }
    
    // Provide clear feedback that the workout saved successfully here.
    
})

Workouts saved on Apple Watch always contribute to the Move and Exercise rings. In iOS 10 and later, workout samples saved on iPhone also contribute to these rings. However, unlike workout objects saved on Apple Watch, the iPhone does not try to calculate the actual amount of time spent exercising. Instead, the system simply increases the Exercise ring by the workout’s total duration.

Following Best Practices

To create the best user experience, please consider the following guidelines:

  • The iOS app can use a WCSession object to determine whether the user has a paired Apple Watch, and whether your corresponding Watch app is installed. If your Watch app is installed on a paired Apple Watch, you can launch the Watch app from your iOS app by calling the health store’s startWatchApp(with:completion:) method.

  • When starting a workout in your watchOS app, use the Watch Connectivity Framework to inform your iOS app about the workout, and to update the iOS app’s UI about the current state of the workout.

  • If the user starts the workout in your watchOS app, and then tries to end it in your iOS app, the iOS app should instruct the user to end the workout in your watchOS app. Otherwise, Apple Watch workout continues to run, which can lead to accidentally saving invalid data to the HealthKit store.

  • If the user starts a workout in your iOS app, and then opens your watchOS app, the watchOS app should automatically start a workout session for the workout in progress. If you save the workout in the watchOS app, you can incorporate data from the iOS workout. For example, you should set the workout’s startDate to the iOS workout’s start. Also, if your app calculates its own calories, you can retroactively give credit for the calories burned before the workout session began.

  • If the user does not start and stop a workout session in your watchOS app, do not try to retroactively create a workout on Apple Watch.

  • Make starting and stopping workouts on Apple Watch as easy and obvious as possible. The app must clearly indicate when a workout session is in progress, and then either automatically save workout data for the user, or provide a clear option to explicitly save or discard workout data. Finally, the watchOS app should provide clear feedback when a workout is successfully saved. For more information, see Health and Fitness in watchOS Human Interface Guidelines.

See Also

Sessions

class HKWorkoutSession

A workout session that tracks the user’s workout on Apple Watch.

protocol HKWorkoutSessionDelegate

The session delegate protocol defines an interface for receiving notifications about errors and changes in the workout session’s state.

enum HKWorkoutSessionState

A workout session’s state.

class HKWorkoutConfiguration

An object that contains configuration information about a workout session.