---
title: "Anatomy of a watchOS Workout App"
canonical: "https://aifitnessapi.com/watch-apps/watchos-workout-app-anatomy"
cluster: "Watch Apps"
primary_query: "build apple watch workout app"
last_reviewed: "2026-08-22"
description: "The spine of every watchOS training app: HKWorkoutSession owns the sensors and the lifecycle, HKLiveWorkoutBuilder assembles the workout sample."
publisher: "AIFitnessAPI — independent, not sponsored"
cite_as: "\"Anatomy of a watchOS Workout App\", AIFitnessAPI, https://aifitnessapi.com/watch-apps/watchos-workout-app-anatomy"
---

# Anatomy of a watchOS Workout App

> A watchOS workout app is two Apple objects plus a state machine you write yourself. HKWorkoutSession is the live half: Apple describes it as a session that tracks a person's workout, and documents that it fine-tunes Apple Watch's sensors for the activity you declare, with all workout sessions generating high-frequency heart rate samples. HKLiveWorkoutBuilder is the record-keeping half, described by Apple as a builder object that constructs a workout incrementally based on live data from an active workout session, and used to create the HKWorkout sample while the session is running. The work your app owns is the lifecycle around them: session state transitions, pause and resume, and the case where the session ends without you asking, because Apple documents that Apple Watch runs one workout session at a time and a second workout ending yours. Treat session state as the single source of truth and render every screen from it.

- Canonical: https://aifitnessapi.com/watch-apps/watchos-workout-app-anatomy
- Last reviewed: 2026-08-22
- Publisher: AIFitnessAPI (https://aifitnessapi.com) — independent, not sponsored
- Cite as: "Anatomy of a watchOS Workout App", AIFitnessAPI, https://aifitnessapi.com/watch-apps/watchos-workout-app-anatomy

---

Strip a watchOS training app down and little is left: two Apple objects and a state machine you write yourself. The metric rings, the interval haptics and the end-of-workout summary all hang off that spine. Getting it wrong produces the class of bug that only appears on somebody else's wrist, in a gym, halfway through a session.

This page is about the app that runs on the watch. Watch data inside a phone app is a different job, covered in [the devices cluster](/devices); the surfaces that pull somebody back is [engagement](/engagement).

## Two objects, one job each

`HKWorkoutSession` is the live half. Apple's abstract is one sentence — "A session that tracks a person's workout" — and Apple lists availability from watchOS 2.0, with iOS, iPadOS and Mac Catalyst from 17.0 and visionOS from 1.0. The session is the object that talks to the hardware on your behalf.

`HKLiveWorkoutBuilder` is the record-keeping half. Apple describes it as "A builder object that constructs a workout incrementally based on live data from an active workout session," and the usage line is equally blunt: "Use a live workout builder to create an HKWorkout sample during an active HKWorkoutSession." Apple documents it from watchOS 5.0, with iOS, iPadOS and Mac Catalyst from 26.0.

State the division in one line before writing any code: the session is physics, the builder is bookkeeping. A session problem shows up as bad or missing live numbers; a builder problem shows up later, as a saved workout that does not match what the user watched happen.

## Declaring the activity is a sensor instruction, not a label

The most consequential line in Apple's session documentation is about the declared activity: "The session fine-tunes Apple Watch's sensors for the specified activity. All workout sessions generate high-frequency heart rate samples; however, an outdoor cycling activity generates accurate location data, while an indoor cycling activity doesn't."

Two things follow. First, high-frequency heart rate is a property of being in a session at all, not something you can request from the store by asking more often — the store-versus-stream distinction is covered in [Apple Watch live heart rate](/devices/apple-watch-live-heart-rate) and in [HealthKit on Apple Watch](/watch-apps/healthkit-on-apple-watch). Second, the activity you declare is an instruction about what the sensor stack should spend power on. Declaring indoor cycling for a ride that goes outside is a decision not to collect accurate location. Pick the activity from the user's real intent, and if somebody can switch activity mid-session, treat that as a state transition rather than a settings toggle. The builder exposes `currentWorkoutActivity` for reading back where you are.

## What the builder does while you are not looking

Apple documents the builder's `dataSource` as "A data source that provides live data from a workout session automatically," and `workoutSession` as "The workout session created by the data source and associated with this builder." Once the data source is attached, live data flows into the accumulating sample without your code shuttling values across.

`shouldCollectWorkoutEvents` is documented as "A Boolean value that determines whether the workout builder automatically adds events generated by the workout session." Decide that flag on purpose. If you also generate your own events — interval boundaries, set markers — know which are yours and which arrived from the session, or the saved workout carries two overlapping narratives of the same hour.

`elapsedTime` is documented as "The elapsed time for the workout based on the builder's current contents, including pauses." Read that last clause twice. Our judgement, not Apple's statement: most training UIs want at least two clocks — total elapsed, and something like moving time — and only one of them is the property above. Derive the others yourself, define each in exactly one place, and render every screen from that definition. Independently computed timers drift, and users find the discrepancy before your tests do.

## The state machine is the part you actually write

Apple gives you a session and a builder, not your app's behavior around start, running, paused, resumed, ended and saved. Write that as an explicit state machine rather than flags scattered across views.

The rule that keeps it honest: session state is authoritative and every view is downstream of it. Do not let a tap on your pause button set an `isPaused` boolean in the view and also ask the session to pause. Send the command, wait for the state change, and render what came back. That sounds fussy until the first time a pause originates somewhere other than your UI — which, as the next section covers, is normal rather than exceptional. Pause and resume also need a policy for your own derived state: interval timers, rest countdowns and rep counters are yours, not the session's, and a resumed session will not restore them for you.

## The session can end without you

Apple is explicit about exclusivity: "Apple Watch runs one workout session at a time. If a second workout starts while your workout is running, your session ends."

So "ended" is a message you receive, not only a thing you request: the user starts a run in Apple's own Workout app, or taps start in a competitor's, and your session is over. Apple also documents Siri control for start, pause, resume and cancel, and mirroring to a companion iPhone — both mean control can originate outside your watch UI. [Mirroring workouts to iPhone](/watch-apps/mirroring-workouts-to-iphone) covers that.

Decide two policies before you ship. What the screen says: a live view still showing a frozen last value after the session ends is lying, and the honest version says data stopped. And what happens to the partial workout, because an unsolicited end leaves you a builder holding real data. Our judgement is to finish and save it, labelled as ended early, rather than discarding somebody's forty minutes because the ending was untidy.

## Where the rest of the app attaches

Staying alive while the wrist is down has its own rules — see [Apple Watch background execution](/watch-apps/apple-watch-background-execution), which also covers why a rehab or mindfulness app cannot use the workout path. Getting a coach's program onto the watch without your app being open is [WorkoutKit scheduled workouts](/watch-apps/workoutkit-scheduled-workouts). Persisting what you collected is [HealthKit](/integrate/healthkit) plus [deduplicating health data](/architecture/deduplicate-health-data), since a phone and a watch writing the same hour is normal.

## Testing the spine

A physical device is the only reliable target for sensors, so put an injectable seam in front of the session: an interface your app talks to, backed by the real session on device and by scripted state transitions in CI. Script the transitions you cannot produce on demand with a real watch — an unsolicited end mid-interval, a pause arriving from Siri while your UI thinks it is running, a resume after a long gap. [Mocking wearable data](/test/mock-wearable-data) and [device lab and CI](/test/device-lab-and-ci) cover the mechanics; [testing watch apps](/watch-apps/testing-watch-apps) covers what still has to happen on hardware.

## FAQ

### Which object owns what between HKWorkoutSession and HKLiveWorkoutBuilder?

The session is the live half and the builder is the record. Apple describes HKWorkoutSession as a session that tracks a person's workout, and documents that it fine-tunes Apple Watch's sensors for the declared activity. Apple describes HKLiveWorkoutBuilder as a builder object that constructs a workout incrementally based on live data from an active workout session, and states you use it to create the HKWorkout sample during an active session. Practically: sensor behavior, lifecycle and exclusivity are session concerns, while accumulated samples, events and the saved result are builder concerns. A fault in one does not present like a fault in the other.

[Permalink](https://aifitnessapi.com/watch-apps/watchos-workout-app-anatomy#faq-1)

### Does elapsedTime on a live workout builder subtract the time a workout spent paused?

No. Apple documents the property as the elapsed time for the workout based on the builder's current contents, including pauses. If your interface promises something like moving time or working time, that is a number you derive rather than one you read. Our advice, offered as judgement: define each clock your product shows in exactly one place in code, drive every screen and every saved summary from that definition, and never let a view compute its own version. Independently computed timers drift, and users notice a mismatch between the ring and the summary long before a test does.

[Permalink](https://aifitnessapi.com/watch-apps/watchos-workout-app-anatomy#faq-2)

### What does shouldCollectWorkoutEvents change about a saved watchOS workout?

Apple documents it as a Boolean value that determines whether the workout builder automatically adds events generated by the workout session. Left on, session-generated events land in the accumulating sample without your involvement; turned off, they do not. Choose deliberately rather than by default, especially if your app also writes its own events for intervals, sets or laps. The failure mode is not a crash but a saved workout carrying two overlapping accounts of the same session, which is confusing in the Health app and worse in any analysis you run over your own history later.

[Permalink](https://aifitnessapi.com/watch-apps/watchos-workout-app-anatomy#faq-3)
