Class

HKWorkoutSession

Use an HKWorkoutSession object to track the user’s workout on Apple Watch.

Overview

The session fine tunes Apple Watch’s sensors for the specified activity. All workout sessions generate higher-frequency heart rate samples; however, an outdoor cycling activity generates more accurate location data, while an indoor cycling activity does not.

Apple Watch runs one workout session at a time. If a second workout starts while your workout is running, your HKWorkoutSessionDelegate object receives an errorAnotherWorkoutSessionStarted error, and your session ends.

Running in the Background

Workout sessions can run in the background. Background running grants an app the following abilities:

  • The app continues to run throughout the entire workout session, even when the user lowers their wrist or interacts with a different app. When the user raises their wrist, the app reappears, letting the user quickly and easily check their current progress and performance.

  • The app can continue to access data from Apple Watch’s sensors in the background, letting you keep the app up to date at all times. For example, a running app can continue to track the user’s heart rate, ensuring that the most recent heart rate data is displayed whenever the user raises their wrist.

  • The app can alert the user using audio or haptic feedback while running in the background.

To maintain high performance on Apple Watch, you must limit the amount of work your app performs in the background. If your app uses an excessive amount of CPU while in the background, watchOS will suspend it. Use Xcode’s CPU report tool or the time profiler in Instruments to test your app’s CPU usage. The system also generates a log with a backtrace whenever your app crosses the CPU threshold and is terminated.

To run in the background, you must add the WKBackgroundModes key to your WatchKit extension’s Info.plist file. This key’s value is an array containing the workout-processing value. For more information, see WKBackgroundModes in Information Property List Key Reference.

To play audio or provide haptic feedback from the background, you must also add the UIBackgroundModes key to your WatchKit extension’s Info.plist file. This key’s value is an array containing the audio value.

Filling the Rings

When used properly, workout sessions let your app contribute to both the Move and Exercise rings in the Activity App. To make sure data from your app appears properly inside the Activity App, you must follow the procedure listed below:

  1. 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.” An exercise minute is generated for each minute of the workout session, and calories are accumulated at a rate no lower than a brisk walk. Calories may be higher, based on 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) ***")
    }
    
  2. 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)
    
  3. Create active energy burned samples to represent the calories burned during the workout session.

    If you want HealthKit to calculate the calories burned, you can 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 {
            // Add proper error handling here...
            print("*** an error occurred: \(error?.localizedDescription) ***")
            return
        }
        
        // create the workout here...
    }
     
    healthStore.executeQuery(query)
    
  4. Create an HKWorkout object to represent the workout session. To contribute to the Move ring, save this workout on Apple Watch.

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

    • The workout object’s total energy should be the sum of all the active energy burned samples created in step 3.

    • For walk, run, or cycle activity types, be sure to 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...
        
    })
    
  5. Associate the active energy burned samples created in step 3 with the workout object created in step 4. This step ensures that your app is shown as the associated app in the Move graph in iOS.

    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 {
            // Add proper error handling here...
            print("*** an error occurred: \(error?.localizedDescription) ***")
            return
        }
        
        // Provide clear feedback that the workout saved successfully here…
        
    })
    

In iOS 10 and later, workout samples saved on iPhone also contribute to the Move and Exercise 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.

Since health data is synced between Apple Watch and iPhone, feel free to create and save workouts 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.

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. When 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 stoping 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 Apple Watch Human Interface Guidelines.

Topics

Creating Workout Sessions

init(configuration: HKWorkoutConfiguration)

Returns a newly instantiated workout session.

Accessing Session Data

var activityType: HKWorkoutActivityType

The workout activity performed during this session.

Deprecated
var delegate: HKWorkoutSessionDelegate?

The workout session’s delegate.

var endDate: Date?

The ending time and date for this workout session.

var locationType: HKWorkoutSessionLocationType

A value that indicates whether the workout session occurred indoors or outdoors.

Deprecated
var startDate: Date?

The starting time and date for this workout session.

var state: HKWorkoutSessionState

The workout session’s current state.

var workoutConfiguration: HKWorkoutConfiguration

The configuration object that describes this workout.

Relationships

Inherits From