← 모든 글

iOS 27의 HealthKit: 운동 존(Workout Zones)과 새로운 타입

HealthKit은 수년 동안 운동 심박수 존을 비공식적인 구성원처럼 다뤄 왔습니다. 5단계 존 막대 차트를 그리는 모든 러닝·사이클링 앱은 그 존을 직접 계산했습니다. 원시 HKQuantitySample 심박수 측정값을 가져와서, 하드코딩했거나 사용자가 입력한 기준값에 맞춰 구간을 나누고, 각 구간에 머문 시간을 일일이 손으로 합산했습니다. iOS 27은 이 수작업을 끝냅니다. 이제 존은 시스템이 생성하고, 사용자가 건강 설정(Health Settings)에서 편집할 수 있으며, 앱은 존별 시간이 이미 집계된 상태로 읽어 오는 구조화된 HealthKit 타입이 됩니다. 같은 릴리스는 HealthKit의 주기 추적 모델에 있던 빈틈을 메우는 두 가지 생식 건강 카테고리 타입, menopausalStatebleedingAfterMenopause도 추가합니다.1

iOS 27의 추가 항목에는 두 갈래의 흐름이 있고, 둘은 같은 모양을 공유합니다. 운동 존 흐름은 그동안 앱이 직접 소유하던 계산을 프레임워크 안으로 옮깁니다. 그러면 한 사람의 존 정의가 그것을 요청하는 모든 앱에서 일관되게 유지됩니다. 새로운 타입 흐름은 그동안 HealthKit이 표현할 수 없었던 생애 단계에 타입이 지정된 카테고리 샘플을 부여합니다. 두 흐름 모두 기존 HealthKit 문법을 따릅니다. 측정값에는 수량 타입(quantity type)을, 이벤트와 상태에는 카테고리 타입(category type)을 씁니다. 이 글은 각 항목을 Apple 문서에 비춰 살펴보면서, 클러스터에서 늘 쓰는 관점을 유지합니다. 즉, 이미 HealthKit을 출시한 앱이 각 기능을 얻기 위해 무엇을 더해야 하는가입니다.

요약(TL;DR)

  • HKWorkoutZoneConfiguration은 수량 타입에 대한 존의 완전한 집합을 정의합니다. 시스템은 사용자의 건강 지표로부터 존을 자동으로 생성하고, 사용자는 건강 설정에서 직접 편집할 수 있으며, 앱은 특정 운동에 맞는 맞춤 존을 제공할 수 있습니다.2
  • HKWorkoutZoneGroup은 존 구성과 존별 시간 데이터를 한데 묶습니다. 완료된 운동 인스턴스에서 이를 읽어 오면, 심박수 샘플을 직접 구간으로 나누는 대신 프레임워크가 이미 계산해 둔 존별 지속 시간을 그대로 얻습니다.3
  • HKHealthStorepreferredWorkoutZoneConfiguration(for:)는 수량 타입에 대해 사용자가 선호하는 존 구성을 반환하므로, 차트가 사용자가 다른 모든 곳에서 보는 기준값과 들어맞게 됩니다.4
  • menopausalStateHKCategoryValueMenopausalState 열거형은 특정 시점의 폐경 상태를 기록합니다. 샘플의 시작 날짜와 종료 날짜가 동일해야 하며, 그렇지 않으면 저장이 실패합니다.56
  • bleedingAfterMenopause는 폐경 후 출혈을 강도 값을 가진 구간으로 기록하며, 생리혈과는 임상적으로 구분됩니다.7

운동 존은 늘 직접 계산해야 할 몫이었습니다

iOS 27 이전에는 심박수 존을 원하는 운동 앱이 모든 일을 도맡았습니다. 운동의 시간 범위에 해당하는 심박수 HKQuantitySample 데이터를 조회하고, 존 경계가 어디에 놓일지를 결정했습니다(220에서 나이를 뺀 값, 젖산 역치 비율, 또는 사용자가 설정 화면에 입력한 값). 그런 다음 샘플을 순서대로 훑으며 각 구간에 머문 초를 누적했습니다. 모든 앱이 저마다의 경계 계산법을 골랐기 때문에, 두 개의 앱으로 달리는 사람은 서로 다른 “존 3”을 보았습니다. 존 데이터는 HealthKit에 전혀 저장되지 않았으므로, 동기화되지도 이식 가능하지도 않았습니다.

iOS 27은 수량 타입에 대한 존의 완전한 집합을 정의하는 구조체인 HKWorkoutZoneConfiguration을 도입합니다.2 이 구성은 순서가 있는 존 배열을 담고, 자신이 적용되는 수량 타입을 식별합니다. 정작 중요한 변화는 존이 어디에서 비롯되는가입니다. Apple 문서에 따르면, 시스템은 사용자의 건강 지표를 바탕으로 존을 자동으로 생성하고, 사용자는 건강 설정에서 직접 존을 구성할 수 있으며, 앱은 특정 운동에 맞는 맞춤 존을 제공할 수 있습니다.2 존 정의는 더 이상 각 앱의 사적인 세부 사항이 아니라, 프레임워크가 소유하는 공유 상태가 됩니다.

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)

짝을 이루는 구조체는 HKWorkoutZoneGroup으로, 수량 타입에 대한 존 구성과 존별 시간 데이터를 담습니다.3 존 그룹은 HKWorkoutZoneConfiguration을 존별 시간 데이터와 결합하므로, 그룹을 읽으면 경계값과 사용자가 각 구간에 머문 시간을 한꺼번에 얻습니다. Apple 문서는 완료된 운동에 대한 존 데이터를 가져오기 위해 완료된 운동 인스턴스에서 존 그룹에 접근하는 방법과, 진행 중인 세션 동안의 실시간 존 정보를 위해 실시간 운동 소스에서 접근하는 방법을 설명합니다.3 구간 나누기는 프레임워크가 합니다. 여러분의 코드는 그 결과를 읽기만 하면 됩니다.

Watch on Apple Developer ↗
Apple은 사용자의 건강 설정에서 앱으로 흘러 들어가는 존 데이터를 보여 주며, 각 존에 머문 시간은 들어오는 샘플로부터 HealthKit이 계산합니다.

세션 207에서 Apple은 운동 존이 HealthKit에 통합되면 운동 중 들어오는 샘플로부터 각 존의 시간이 자동으로 계산되므로, 앱은 심박수 샘플을 직접 구간으로 나누는 대신 zoneGroupsByType에서 미리 합산된 지속 시간을 읽어 온다고 설명합니다.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)
    }
}

세 번째 조각은 구성을 앱이 아니라 사용자에게 묶어 둡니다. preferredWorkoutZoneConfiguration(for:)HKHealthStore의 인스턴스 메서드로, 지정한 수량 타입에 대해 사용자가 선호하는 존 구성을 반환합니다:4

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

Apple 문서는 반환 계약을 분명히 명시합니다. 이 메서드는 사용자가 건강 설정에서 직접 구성한 존을 돌려주거나, 사용자가 맞춤 값을 설정하지 않았다면 시스템이 생성한 존을 돌려주며, 사용자가 해당 수량 타입에 대해 존을 전혀 구성하지 않았다면 nil을 돌려줍니다.4 시스템이 생성한 존은 사용자의 건강 지표가 변함에 따라 주기적으로 갱신됩니다. 반면 사용자가 직접 설정한 존은 다시 편집하기 전까지 그대로 유지됩니다. 문서가 밝히는 의도는, 앱이 모든 운동에 걸쳐 사용자의 선호와 들어맞는 존 정보를 표시하도록 하는 것입니다.4 자신만의 존을 만들어 내는 대신 여러분의 존 3이 사용자가 다른 모든 곳에서 보는 존 3과 일치하기를 바란다면, 이 메서드를 사용하세요.

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)
}

이 비대칭은 짚어 둘 가치가 있습니다. preferredWorkoutZoneConfiguration(for:)는 사용자의 고정된 선호를 읽어 오며, 이는 운동 전·중·후에 걸쳐 일관된 차트를 그립니다. 여러분이 직접 만드는 맞춤 HKWorkoutZoneConfiguration은 특정 운동이 사용자의 기본값과 다른 존을 필요로 하는 경우, 예컨대 자체 구간을 갖는 구조화된 인터벌 세션 같은 경우를 위한 것입니다. 대부분의 앱은 선호 구성을 원하며, 맞춤 경로는 운동의 구조가 자체 존을 요구할 때를 위한 것입니다.

새로운 카테고리 타입: menopausalStatebleedingAfterMenopause

HealthKit의 주기 추적 모델은 월경은 다뤘지만, 사용자가 폐경 이행기의 어느 지점에 있는지를 기록할 타입화된 방법이 없었고, 월경이 끝난 뒤에 일어나는 출혈을 기록할 방법도 없었습니다. iOS 27은 둘 모두를 카테고리 타입으로 추가하며, iOS 26 HealthKit 글이 마음챙김 세션과 수면을 다룰 때 소개한 것과 같은 HKCategorySample 패턴을 따릅니다.

menopausalState는 사용자의 폐경 상태를 기록하는 샘플을 위한 카테고리 타입 식별자입니다.5 각 샘플은 특정 시점의 항목이며, Apple 문서는 단호한 제약을 둡니다. 시작 날짜는 종료 날짜와 같아야 하고, 둘이 다른 샘플을 저장하면 오류가 발생합니다.5 각 샘플의 값은 HKCategoryValueMenopausalState 열거형의 한 케이스이며, 이는 기록된 시점의 폐경 상태를 나타냅니다.6 Apple의 설명에서는 팩트시트의 케이스 목록이 생략되어 있으므로, 케이스 이름을 추측하지 말고 유효한 값에 대해서는 이 열거형을 진실의 원천으로 삼으세요. 문서가 명시하는 동작은, 각 샘플이 특정 날짜에 그러한 케이스 하나를 저장한다는 것입니다.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)
}

이 시점 기반 모델은 데이터를 해석하는 방식을 결정합니다. Apple 문서는 앱이 시간에 걸쳐 여러 샘플을 읽어 더 높은 수준의 해석, 즉 활성 기간이나 이행, 또는 현재 상태를 도출할 수 있다고 설명합니다.6 단일 샘플은 특정 상태가 특정 날짜에 적용되었음을 주장하며, 샘플들을 하나의 타임라인으로 엮는 작업은 프레임워크가 앱에게 맡깁니다. Apple은 앱이 그 일련의 샘플을 어떻게 읽느냐에 따라, 각 항목을 상태 변화로, 그 날짜에 어떤 상태가 적용되었다는 확인으로, 또는 그 둘 모두로 봅니다.5

bleedingAfterMenopause는 폐경 후 출혈을 기록하는 샘플을 위한 카테고리 타입 식별자입니다.7 임상적 구분이 이 타입이 따로 존재하는 이유입니다. 폐경 이후에는 월경이 끝났으므로, 폐경 후 출혈은 생리혈도 아니고 월경 간 출혈도 아닙니다. Apple 문서는 이를 둘 모두와 임상적으로 구분된다고 부릅니다.7 시점 기반인 폐경 상태 샘플과 달리, bleedingAfterMenopause 샘플은 출혈의 구간을 나타내며 강도 값을 저장합니다.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)
}

두 타입 사이의 갈라짐은 iOS 26 글이 시점 기반과 구간 기반 카테고리 샘플 사이에 그었던 갈라짐을 그대로 비춥니다. menopausalState는 그 시간 창을 하나의 순간으로 압축하고 상태를 담습니다. bleedingAfterMenopause는 실제 [start, end] 창을 유지하고 강도를 담습니다. 둘 중 어느 쪽이든 잘못된 모양을 고르면 저장 오류가 나거나 무의미한 기록이 되므로, 여러분이 고르는 타입은 쿼리 코드를 한 줄 쓰기도 전에 데이터의 성질을 결정짓습니다.

도입 경로

이미 HealthKit을 출시한 운동 앱이나 건강 앱은 iOS 27 표면을 점진적으로 추가합니다. 그중 어느 것도 iOS 26 패턴 글이 다룬 권한 부여나 샘플 저장 배관을 다시 짜지 않습니다.

  1. 존 계산 코드를 지우세요. 심박수 존을 직접 계산하는 앱이라면, 해당 수량 타입에 대해 preferredWorkoutZoneConfiguration(for:)을 호출하고 반환된 구성을 렌더링해야 하며, 호출이 nil을 반환할 때만 자신의 기본값으로 대체해야 합니다.4 이제 여러분의 존은 사용자가 시스템 전반에서 보는 것과 일치합니다.
  2. 샘플을 구간으로 나누는 대신 HKWorkoutZoneGroup을 읽으세요. 완료된 운동 요약의 경우, 원시 심박수 샘플을 훑으며 지속 시간을 직접 누적하지 말고 존 그룹을 가져와 미리 계산된 존별 시간 데이터를 렌더링하세요.3
  3. 운동이 자체 구간을 필요로 할 때만 맞춤 HKWorkoutZoneConfiguration을 제공하세요. 고유한 존을 갖는 구조화된 인터벌 세션이 맞춤 구성을 쓸 경우이고, 그 밖의 모든 경우에는 사용자의 선호 구성을 씁니다.2
  4. 새로운 카테고리 타입을 권한 요청에 추가하세요. 주기 추적 앱은 공유 및 읽기 집합에 menopausalStatebleedingAfterMenopause를 추가한 다음, menopausalState는 시점 기반 샘플(시작과 종료가 동일)로, bleedingAfterMenopause는 강도 값을 가진 구간 샘플로 기록합니다.57
  5. 타입별로 시작/종료 날짜를 점검하세요. 폐경 상태 저장은 시작과 종료가 다르면 실패하고, 출혈 샘플은 구간이므로 둘이 다르기를 요구합니다. 저장 경로를 출시하기 전에 시간 창의 모양을 올바르게 맞추세요.57

자주 묻는 질문(FAQ)

iOS 27에서도 심박수 존을 직접 계산해야 하나요?

아니요, 일반적인 경우에는 그렇지 않습니다. preferredWorkoutZoneConfiguration(for:)은 수량 타입에 대해 사용자가 선호하는 존 구성(건강 설정에서 직접 구성한 존, 또는 시스템이 생성한 존, 또는 아무것도 구성하지 않았다면 nil)을 반환하고, HKWorkoutZoneGroup은 프레임워크가 운동에 대해 이미 계산해 둔 존별 시간 데이터를 담습니다.34 직접 존을 계산해야 하는 경우는 특정 운동이 사용자의 기본값과 다른 맞춤 구간을 필요로 할 때뿐이며, 그때는 그 운동을 위한 HKWorkoutZoneConfiguration을 만들면 됩니다.2

HKWorkoutZoneConfigurationHKWorkoutZoneGroup의 차이는 무엇인가요?

HKWorkoutZoneConfiguration은 수량 타입에 대한 존의 완전한 집합, 즉 순서가 있는 존과 그것이 적용되는 수량 타입을 정의합니다.2 HKWorkoutZoneGroup은 그 구성과 수량 타입에 대한 존별 시간 데이터를 모두 담으므로, 그룹은 경계값과 사용자가 각 존에 머문 시간을 알려 줍니다.3 완료된 세션의 경우 완료된 운동 인스턴스에서, 실시간 존 정보의 경우 실시간 소스에서 존 그룹을 읽습니다.3

사용자가 존을 한 번도 설정한 적이 없을 때 preferredWorkoutZoneConfiguration(for:)은 무엇을 반환하나요?

사용자가 맞춤 값을 설정하지 않았다면 시스템이 생성한 존을 반환하고, 사용자가 해당 수량 타입에 대해 존을 전혀 구성하지 않은 경우에만 nil을 반환합니다.4 사용자가 건강 설정에서 존을 직접 설정했다면 그 존을 반환하며, 그 존은 사용자가 편집하기 전까지 그대로 유지됩니다. 시스템이 생성한 존은 사용자의 건강 지표가 변함에 따라 주기적으로 갱신됩니다.4 nil에 분기를 두어 여러분만의 대체 기본값을 제공하세요.

menopausalState 샘플을 저장할 때 왜 날짜 오류로 실패하나요?

menopausalState는 단일 시점의 상태를 기록하기 때문에, 프레임워크는 샘플의 시작 날짜와 종료 날짜가 동일하기를 요구하며, 날짜가 다른 샘플은 저장 시 오류를 일으킵니다.5 둘을 같은 Date로 설정하세요. 이 제약이 menopausalState를, 구간을 나타내며 시작과 종료가 다르기를 요구하는 bleedingAfterMenopause와 구분 짓습니다.7

bleedingAfterMenopause는 생리혈과 어떻게 다른가요?

폐경 이후에는 월경이 끝났으므로, 폐경 후 출혈은 생리혈과도 월경 간 출혈과도 임상적으로 구분됩니다. Apple이 이를 위해 별도의 카테고리 타입을 부여한 이유가 바로 그것입니다.7bleedingAfterMenopause 샘플은 구간과 강도 값을 저장하며, 관련된 상태 추적에는 menopausalState 타입을 사용합니다.7

이 글 곁에는 Apple 생태계 클러스터 전체가 함께 놓여 있습니다. 이 글이 기반으로 삼는 iOS 26 HealthKit 권한 부여 및 샘플 타입 패턴, 실시간 세션 동안 존 데이터가 비롯되는 watchOS 운동 생애 주기, 손목에서의 백그라운드 실행을 규율하는 watchOS 런타임 계약, 그리고 HealthKit을 데이터 소스 계층으로 자리매김하는 iOS 앱의 세 가지 표면이 그것입니다. 허브는 Apple 생태계 시리즈입니다. AI 에이전트와 함께하는 더 넓은 iOS 맥락은 iOS 에이전트 개발 가이드를 참고하세요.

참고 자료


  1. Apple Developer Documentation: HealthKit. 수량 타입, 카테고리 타입, 샘플, 그리고 iOS 27의 운동 존 및 생식 건강 추가 항목을 다루는 프레임워크 레퍼런스. 

  2. Apple Developer Documentation: HKWorkoutZoneConfiguration (iOS 27.0). “A structure that defines a complete set of zones for a quantity type.” struct HKWorkoutZoneConfiguration으로 선언됨. 순서가 있는 존 배열을 담고, 자신의 수량 타입을 식별하며, 시스템 생성·사용자 구성·앱 제공 맞춤 존을 지원함. 

  3. Apple Developer Documentation: HKWorkoutZoneGroup (iOS 27.0). “A structure that contains zone configuration and time-in-zone data for a quantity type.” struct HKWorkoutZoneGroup으로 선언됨. 구성과 존별 시간 데이터를 결합하며, 완료된 운동 인스턴스와 실시간 운동 소스에서 접근 가능함. 

  4. Apple Developer Documentation: preferredWorkoutZoneConfiguration(for:) (iOS 27.0). “Returns someone’s preferred zone configuration for the specified quantity type.” func preferredWorkoutZoneConfiguration(for quantityType: HKQuantityType) async throws -> HKWorkoutZoneConfiguration?으로 선언됨. 직접 구성한 존, 시스템 생성 존, 또는 nil을 반환함. 

  5. Apple Developer Documentation: menopausalState (iOS 27.0). “An identifier for samples that record a person’s menopausal state.” static let menopausalState: HKCategoryTypeIdentifier으로 선언됨. 시점 기반 샘플은 시작과 종료 날짜가 동일해야 하며, 그렇지 않으면 저장이 실패함. 

  6. Apple Developer Documentation: HKCategoryValueMenopausalState (iOS 27.0). “A value that indicates the menopausal state at a recorded point in time.” enum HKCategoryValueMenopausalState으로 선언됨. 앱은 시간에 걸쳐 여러 샘플을 조회해 활성 기간, 이행, 또는 현재 상태를 도출할 수 있음. 

  7. Apple Developer Documentation: bleedingAfterMenopause (iOS 27.0). “An identifier for samples that record bleeding after menopause.” static let bleedingAfterMenopause: HKCategoryTypeIdentifier으로 선언됨. 생리혈과 임상적으로 구분되며, 각 샘플은 강도 값을 가진 구간임. 

  8. Apple, WWDC26 세션 207, “Deliver workout insights with HealthKit workout zones.” developer.apple.com/videos/play/wwdc2026/207. iOS 27에서 심박수 및 사이클링 파워 존이 HealthKit에 통합되면서, 운동 중 들어오는 샘플을 바탕으로 각 존의 시간이 자동으로 계산되고, 앱은 이를 HKWorkout 또는 HKWorkoutActivityzoneGroupsByType 딕셔너리에서 읽어 옴. 

관련 게시물

iOS 27의 SwiftData: Observation과 History

iOS 27은 SwiftData에 ResultsObserver를 통한 일급 변경 감지, HistoryObserver를 통한 영구 히스토리 관찰, 그리고 codable 속성 저장을 제공합니다.

8 분 소요

iOS 27의 SwiftUI 새 기능

iOS 27은 SwiftUI의 리스트, 문서, 툴바, 오류 처리를 재설계합니다. 드래그 재정렬, 읽기/쓰기 문서 모델, 툴바 오버플로, 항목 기반 알림이 추가됩니다.

18 분 소요

The Robots Are Taking Exams in My Search Console

First-party GSC data: 91% of 3.8M impressions fail a human-query filter. Exam questions, pasted errors, and agent sweeps…

10 분 소요