Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2025 App ServicesHealth & Fitness

WWDC25 · 11 min · App Services / Health & Fitness

Track workouts with HealthKit on iOS and iPadOS

Learn best practices for building a great workout experience for iOS. Review the life cycle of a workout session, explore the differences between workouts on Apple Watch and iPhone, and find out how to use Live Activities and Siri to pump up your app’s Lock Screen experience.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 9 snippets

Set up workout session swift · at 1:30 ↗
// Set up workout session

// Create workout configuration
let configuration = HKWorkoutConfiguration()
configuration.activityType = .running
configuration.locationType = .outdoor

// Create workout session
let session = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
session.delegate = self

// Get associated workout builder and add data source
let builder = session.associatedWorkoutBuilder()
builder.delegate = self
builder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore,
                                             workoutConfiguration: configuration)
Starting the session swift · at 1:54 ↗
// Prepare and start session

session.prepare()

// Start and display count down

// Start session and builder collection once count down finishes
session.startActivity(with: startDate)
try await builder.beginCollection(at: startDate)
Handling Metrics swift · at 2:14 ↗
// Handling collected metrics

func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, 
                    didCollectDataOf collectedTypes: Set<HKSampleType>) {
    for type in collectedTypes {
        guard let quantityType = type as? HKQuantityType else { return }

        let statistics = workoutBuilder.statistics(for: quantityType)

        // Update the published values
        updateForStatistics(statistics)
    }
}
Ending workout swift · at 2:28 ↗
// Stopping the workout session

session.stopActivity(with: .now)

// Session transitions to stopped then call end
func workoutSession(_ workoutSession: HKWorkoutSession,
                    didChangeTo toState: HKWorkoutSessionState,
                    from fromState: HKWorkoutSessionState,
                    date: Date) {
    guard change.newState == .stopped, let builder else { return }
     
    try await builder.endCollection(at: change.date)
    let finishedWorkout = try await builder.finishWorkout()
    session.end()
}
Set up Siri Intent swift · at 7:17 ↗
// Create an INExtension within your main app

// Define an intent handler
public class IntentHandler: INExtension {

}

// Define the intents to support
extension IntentHandler: INStartWorkoutIntentHandling

extension IntentHandler: INPauseWorkoutIntentHandling

extension IntentHandler: INResumeWorkoutIntentHandling

extension IntentHandler: INEndWorkoutIntentHandling
Handle the Siri intent swift · at 7:32 ↗
// Handle the intent

public func handle(intent: INStartWorkoutIntent) async -> INStartWorkoutIntentResponse {
    let state = await WorkoutManager.shared.state
        
    switch state {
    case .running, .paused, .prepared, .stopped:
        return INStartWorkoutIntentResponse(code: .failureOngoingWorkout, 
                                            userActivity: nil)
    default:
        break;
    }
    Task {
        await MainActor.run {
            // Handle the intents activity type and location
            WorkoutManager.shared.setWorkoutConfiguration(activityType: .running,   
                                                          location: .outdoor)
        }
    }
    return INStartWorkoutIntentResponse(code: .success, userActivity: nil)
 }
App Delegate swift · at 7:52 ↗
// Implement an app delegate

// Create app delegate
class WorkoutsOniOSSampleAppDelegate: NSObject, UIApplicationDelegate {
    let handler = IntentHandler()

    func application(_ application: UIApplication, handlerFor intent: INIntent) -> Any? {
        return handler
    }
}

// Add app delegate to app
struct WorkoutsOniOSSampleApp: App {
    @UIApplicationDelegateAdaptor(WorkoutsOniOSSampleAppDelegate.self) var appDelegate

}
Set up crash recovery swift · at 9:09 ↗
// App Delegate

func application(_ application: UIApplication,
                 configurationForConnecting connectingSceneSession: UISceneSession,
                 options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    if options.shouldHandleActiveWorkoutRecovery {
        let store = HKHealthStore()
        store.recoverActiveWorkoutSession(completion: { (workoutSession, error) in
            // Handle error
            Task {
                await WorkoutManager.shared.recoverWorkout(recoveredSession: workoutSession)
            }
        })
    }
    let configuration = UISceneConfiguration(name: "Default Configuration", 
                                             sessionRole: connectingSceneSession.role)
    configuration.delegateClass = WorkoutsOniOSSampleAppSceneDelegate.self
    return configuration
}
Recover the workout session swift · at 9:25 ↗
// Recover the workout for the session


func recoverWorkout(recoveredSession: HKWorkoutSession) {
    session = recoveredSession
    builder = recoveredSession.associatedWorkoutBuilder()
    session?.delegate = self
    builder?.delegate = self
    workoutConfiguration = recoveredSession.workoutConfiguration

    let dataSource = HKLiveWorkoutDataSource(healthStore: healthStore,                                                                  
                                             workoutConfiguration: workoutConfiguration)
    builder?.dataSource = dataSource
}

Resources