HealthKit in iOS 27: Workout Zones, New Types

HealthKit has carried workout heart-rate zones as an unofficial citizen for years: every running and cycling app that drew a five-zone bar chart computed those zones itself, pulled raw HKQuantitySample heart-rate readings, bucketed them against hard-coded or user-entered thresholds, and summed the time in each bucket by hand. iOS 27 ends the hand-rolling. Zones become a structured HealthKit type the system generates, the person can edit in Health Settings, and your app reads back with time-in-zone already tallied. The same release adds two reproductive-health category types, menopausalState and bleedingAfterMenopause, that close a gap in HealthKit’s cycle-tracking model.1

Two threads run through the iOS 27 additions, and they share a shape. The workout-zone thread takes computation apps used to own and moves it into the framework, where one person’s zone definition stays consistent across every app that asks. The new-types thread gives life stages HealthKit could not previously represent a typed category sample. Both follow the existing HealthKit grammar: quantity types for measured values, category types for events and states. The post walks each against Apple’s documentation, with the cluster’s usual frame: what an app that already ships HealthKit adds to gain each capability.

TL;DR

  • HKWorkoutZoneConfiguration defines a complete set of zones for a quantity type. The system generates zones automatically from a person’s health metrics, the person can edit them in Health Settings, and an app can supply custom zones for a specific workout.2
  • HKWorkoutZoneGroup pairs a zone configuration with its time-in-zone data. You read it back from completed-workout instances to get per-zone durations the framework already computed, instead of bucketing heart-rate samples yourself.3
  • preferredWorkoutZoneConfiguration(for:) on HKHealthStore returns the person’s preferred zone configuration for a quantity type, so your charts line up with the thresholds they see everywhere else.4
  • menopausalState plus the HKCategoryValueMenopausalState enumeration record menopausal state at a point in time; the sample’s start date and end date must be identical or the save fails.56
  • bleedingAfterMenopause records post-menopausal bleeding as an interval with an intensity value, kept clinically distinct from menstrual flow.7

Workout Zones Were Always Yours to Compute

Before iOS 27 a workout app that wanted heart-rate zones did the entire job. It queried heart-rate HKQuantitySample data for the workout’s time range, decided where the zone boundaries sat (220-minus-age, lactate-threshold percentages, or a value the user typed into a settings screen), walked the samples in order, and accumulated the seconds spent in each band. Every app picked its own boundary math, so a person who ran with two apps saw two different “Zone 3.” None of the zone data lived in HealthKit, so none of it synced or stayed portable.

iOS 27 introduces HKWorkoutZoneConfiguration, a structure that defines a complete set of zones for a quantity type.2 The configuration carries an ordered array of zones and identifies the quantity type it applies to. The change that matters is where the zones originate. Apple’s documentation states the system generates zones automatically based on a person’s health metrics; the person can configure zones manually in Health Settings; or an app can provide custom zones for specific workouts.2 Zone definitions stop being a private detail of each app and become shared state the framework owns.

import HealthKit

let heartRateType = HKQuantityType(.heartRate)

// A custom configuration an app supplies for one workout.
// makeIntervalZones is your own builder; construct the configuration
// from the ordered zones your workout uses, per HKWorkoutZoneConfiguration's
// declaration. The configuration identifies the quantity type the zones apply to.
let configuration: HKWorkoutZoneConfiguration = makeIntervalZones(for: heartRateType)

The companion structure is HKWorkoutZoneGroup, which contains zone configuration and time-in-zone data for a quantity type.3 A zone group combines an HKWorkoutZoneConfiguration with the per-zone time data, so reading a group hands you both the boundaries and how long the person spent inside each one. Apple’s documentation describes accessing zone groups from completed-workout instances to retrieve zone data for finished workouts, and from a live-workout source for real-time zone information during an active session.3 The framework does the bucketing. Your code reads the result.

Watch: Deliver workout insights with HealthKit workout zones (WWDC26) Apple shows zone data flowing from a person’s Health Settings into the app, with the time spent in each zone computed by HealthKit from incoming samples.

In session 207, Apple states that with workout zones integrated into HealthKit the time in each zone is calculated automatically from the incoming samples during the workout, so the app reads pre-summed durations from zoneGroupsByType instead of bucketing heart-rate samples itself.8

import HealthKit

func summarize(_ group: HKWorkoutZoneGroup) {
    // The group bundles the configuration (the zone boundaries)
    // with the time-in-zone data the framework already computed.
    let configuration = group.configuration

    for zone in group.zones {
        // Render each zone's accumulated time. No sample-walking,
        // no manual bucketing — the durations arrive pre-summed.
        render(zone)
    }
}

The third piece ties the configuration to the person rather than the app. preferredWorkoutZoneConfiguration(for:) is an instance method on HKHealthStore that returns someone’s preferred zone configuration for the specified quantity type:4

func preferredWorkoutZoneConfiguration(
    for quantityType: HKQuantityType
) async throws -> HKWorkoutZoneConfiguration?

Apple’s documentation spells out the return contract: the method gives back the person’s manually configured zones from Health Settings, or the system-generated zones if the person has not set custom values, or nil if they have not configured zones for that quantity type at all.4 System-generated zones update periodically as the person’s health metrics change; zones the person set by hand stay constant until they edit them again. The documented intent is that apps display zone information that aligns with the person’s preferences across every workout.4 Reach for the method when you want your Zone 3 to match the Zone 3 the person sees everywhere else, instead of inventing your own.

import HealthKit

let store = HKHealthStore()
let heartRateType = HKQuantityType(.heartRate)

func loadPreferredZones() async throws -> HKWorkoutZoneConfiguration? {
    // nil means the person has not configured zones for heart rate.
    // Fall back to your own defaults only in that case.
    try await store.preferredWorkoutZoneConfiguration(for: heartRateType)
}

The asymmetry is worth naming. preferredWorkoutZoneConfiguration(for:) reads the person’s standing preference, which draws consistent charts before, during, and after a workout. A custom HKWorkoutZoneConfiguration you build yourself covers the case where a specific workout needs zones that differ from the person’s default, such as a structured interval session with its own bands. Most apps want the preferred configuration; the custom path is for when a workout’s structure demands its own zones.

New Category Types: menopausalState and bleedingAfterMenopause

HealthKit’s cycle-tracking model handled menstruation, but it had no typed way to record where a person sat in the menopausal transition, and no way to record bleeding that occurs after menstruation has ended. iOS 27 adds both as category types, following the same HKCategorySample pattern the iOS 26 HealthKit post covered for mindful sessions and sleep.

menopausalState is a category type identifier for samples that record a person’s menopausal state.5 Each sample is a point-in-time entry, and Apple’s documentation imposes a hard constraint: the start date must equal the end date, and saving a sample where the two differ produces an error.5 The value of each sample is a case of the HKCategoryValueMenopausalState enumeration, which indicates the menopausal state at a recorded point in time.6 Because the fact-sheet’s case list is elided in Apple’s discussion, treat the enumeration as the source of truth for the valid values rather than guessing at case names; the documented behavior is that each sample stores one such case at a specific date.6

import HealthKit

let store = HKHealthStore()
let menopausalType = HKCategoryType(.menopausalState)

func record(state: HKCategoryValueMenopausalState, on date: Date) async throws {
    // Point-in-time sample: start and end MUST be identical.
    // A mismatched start/end is a save error, by documented design.
    let sample = HKCategorySample(
        type: menopausalType,
        value: state.rawValue,
        start: date,
        end: date
    )
    try await store.save(sample)
}

The point-in-time model shapes how you interpret the data. Apple’s documentation notes that apps can read multiple samples over time to derive higher-level interpretations: active periods, transitions, or a current state.6 A single sample claims that a particular state applied on a particular date, and the framework leaves the work of stitching samples into a timeline to the app. Apple frames each entry as a state change, a confirmation that a state applied at that date, or both, depending on how the app reads the series.5

bleedingAfterMenopause is a category type identifier for samples that record bleeding after menopause.7 The clinical distinction is the reason it exists as its own type. After menopause, menstruation has ended, so post-menopausal bleeding is not menstrual flow and not intermenstrual bleeding; Apple’s documentation calls it clinically distinct from both.7 Unlike the point-in-time menopausal-state sample, a bleedingAfterMenopause sample represents an interval of bleeding and stores an intensity value.7

import HealthKit

let store = HKHealthStore()
let bleedingType = HKCategoryType(.bleedingAfterMenopause)

// `intensityValue` is the raw value of the intensity enum Apple defines for
// the bleedingAfterMenopause type; confirm the concrete case set in its declaration.
func record(intensityValue: Int, from start: Date, to end: Date) async throws {
    // An interval sample, not point-in-time: start and end differ,
    // and the value carries the bleeding intensity.
    let sample = HKCategorySample(
        type: bleedingType,
        value: intensityValue,
        start: start,
        end: end
    )
    try await store.save(sample)
}

The split between the two types mirrors the split the iOS 26 post drew between point-in-time and interval category samples. menopausalState collapses its window to a single instant and carries a state. bleedingAfterMenopause keeps a real [start, end] window and carries an intensity. Picking the wrong shape for either one is a save error or a meaningless record, so the type you choose encodes the data’s nature before you write a line of query code.

Adoption Path

A workout or health app that already ships HealthKit adds the iOS 27 surface incrementally; none of it rewrites the authorization or sample-save plumbing the iOS 26 patterns post covered.

  1. Delete your zone math. Any app computing heart-rate zones by hand should call preferredWorkoutZoneConfiguration(for:) for the relevant quantity type and render the returned configuration, falling back to your own defaults only when the call returns nil.4 Your zones now match what the person sees system-wide.
  2. Read HKWorkoutZoneGroup instead of bucketing samples. For completed-workout summaries, pull the zone group and render its pre-computed time-in-zone data rather than walking raw heart-rate samples and accumulating durations yourself.3
  3. Supply a custom HKWorkoutZoneConfiguration only when a workout needs its own bands. Structured interval sessions with bespoke zones are the case for a custom configuration; everything else uses the person’s preferred one.2
  4. Add the new category types to your authorization request. A cycle-tracking app adds menopausalState and bleedingAfterMenopause to its share and read sets, then records menopausalState as point-in-time samples (start equals end) and bleedingAfterMenopause as interval samples with an intensity.57
  5. Audit your start/end dates per type. The menopausal-state save fails if start and end differ; the bleeding sample is an interval and expects them to differ. Get the window shape right before shipping the save path.57

FAQ

Do I still have to compute heart-rate zones myself in iOS 27?

No, not in the common case. preferredWorkoutZoneConfiguration(for:) returns the person’s preferred zone configuration for a quantity type (their manually configured zones from Health Settings, or system-generated zones, or nil if they have not configured any), and HKWorkoutZoneGroup carries the time-in-zone data the framework already computed for a workout.34 You compute zones yourself only when a specific workout needs custom bands that differ from the person’s default, in which case you build an HKWorkoutZoneConfiguration for that workout.2

What is the difference between HKWorkoutZoneConfiguration and HKWorkoutZoneGroup?

HKWorkoutZoneConfiguration defines a complete set of zones for a quantity type: the ordered zones and the quantity type they apply to.2 HKWorkoutZoneGroup contains both that configuration and the time-in-zone data for a quantity type, so a group tells you the boundaries and how long the person spent in each zone.3 You read zone groups from completed-workout instances for finished sessions and from a live source for real-time zone information.3

What does preferredWorkoutZoneConfiguration(for:) return when the person never set up zones?

It returns the system-generated zones if the person has not set custom values, and nil only if the person has not configured zones for that quantity type at all.4 When the person set zones manually in Health Settings, the method returns those, and they stay constant until the person edits them; system-generated zones update periodically as the person’s health metrics change.4 Branch on nil to supply your own fallback defaults.

Why does saving a menopausalState sample fail with a date error?

Because menopausalState records a state at a single point in time, the framework requires the sample’s start date and end date to be identical, and a sample whose dates differ produces an error on save.5 Set both to the same Date. That constraint distinguishes it from bleedingAfterMenopause, which represents an interval and expects the start and end to differ.7

How is bleedingAfterMenopause different from menstrual flow?

After menopause, menstruation has ended, so post-menopausal bleeding is clinically distinct from menstrual flow and from intermenstrual bleeding; Apple gives it a separate category type for that reason.7 Each bleedingAfterMenopause sample stores an interval and an intensity value, and for the related state tracking you use the menopausalState type.7

The full Apple Ecosystem cluster sits alongside this post: the iOS 26 HealthKit authorization and sample-type patterns this builds on; the watchOS workout lifecycle where zone data originates during a live session; the watchOS runtime contract that governs background execution on the wrist; and the three surfaces of an iOS app that place HealthKit as the data-source layer. The hub is the Apple Ecosystem Series. For the broader iOS-with-AI-agents context, see the iOS Agent Development guide.

References


  1. Apple Developer Documentation: HealthKit. The framework reference covering quantity types, category types, samples, and the iOS 27 workout-zone and reproductive-health additions. 

  2. Apple Developer Documentation: HKWorkoutZoneConfiguration (iOS 27.0). “A structure that defines a complete set of zones for a quantity type.” Declared as struct HKWorkoutZoneConfiguration; contains an ordered array of zones, identifies its quantity type, and supports system-generated, person-configured, or app-supplied custom zones. 

  3. Apple Developer Documentation: HKWorkoutZoneGroup (iOS 27.0). “A structure that contains zone configuration and time-in-zone data for a quantity type.” Declared as struct HKWorkoutZoneGroup; combines a configuration with time-in-zone data, accessible from completed-workout instances and from live-workout sources. 

  4. Apple Developer Documentation: preferredWorkoutZoneConfiguration(for:) (iOS 27.0). “Returns someone’s preferred zone configuration for the specified quantity type.” Declared as func preferredWorkoutZoneConfiguration(for quantityType: HKQuantityType) async throws -> HKWorkoutZoneConfiguration?; returns manually configured zones, system-generated zones, or nil

  5. Apple Developer Documentation: menopausalState (iOS 27.0). “An identifier for samples that record a person’s menopausal state.” Declared as static let menopausalState: HKCategoryTypeIdentifier; point-in-time samples require identical start and end dates or the save fails. 

  6. Apple Developer Documentation: HKCategoryValueMenopausalState (iOS 27.0). “A value that indicates the menopausal state at a recorded point in time.” Declared as enum HKCategoryValueMenopausalState; apps can query multiple samples over time to derive active periods, transitions, or current state. 

  7. Apple Developer Documentation: bleedingAfterMenopause (iOS 27.0). “An identifier for samples that record bleeding after menopause.” Declared as static let bleedingAfterMenopause: HKCategoryTypeIdentifier; clinically distinct from menstrual flow, each sample is an interval with an intensity value. 

  8. Apple, WWDC26 session 207, “Deliver workout insights with HealthKit workout zones.” developer.apple.com/videos/play/wwdc2026/207. With heart rate and cycling power zones integrated into HealthKit in iOS 27, the time in each zone is calculated automatically based on incoming samples during the workout, and an app reads it back from the zoneGroupsByType dictionary on HKWorkout or HKWorkoutActivity

관련 게시물

SwiftData in iOS 27: Observation and History

iOS 27 gives SwiftData first-class change observation with ResultsObserver, remote-history monitoring with HistoryObserv…

11 분 소요

What's New in SwiftUI for iOS 27

iOS 27 reworks SwiftUI lists, documents, toolbars, and errors: drag-to-reorder, a readable/writable document model, tool…

23 분 소요

Your Agent Has Two Untrusted Inputs

AI agents have two untrusted inputs: code the model writes and tool output it reads. One now has a real WASM sandbox; th…

12 분 소요