iOS 27 integrates heart rate and cycling power zone tracking directly into HealthKit, automatically calculating time spent in each intensity zone during workouts and delivering live zone-change updates to your app.
⢠Apps can now retrieve structured zone duration data from completed workouts without manually binning samples ā HealthKit does the math automatically.
⢠Live delegate callbacks fire whenever the user crosses a zone boundary mid-workout, enabling real-time coaching UI with zero polling.
⢠Zone configurations sync across devices via HealthKit, and apps can supply custom zone thresholds (e.g. a 7-zone power model) before collection begins.
Fetches the most recent completed workout from HealthKit and displays the time spent in each heart rate zone alongside the zone boundaries, demonstrating the new zoneGroupsByType API.
import SwiftUI
import HealthKit
struct WorkoutZoneSummaryView: View {
@State private var zoneDurations: [HKWorkoutZoneDuration] = []
@State private var statusMessage: String = "Tap to load zones"
private let healthStore = HKHealthStore()
var body: some View {
NavigationStack {
List {
Section("Heart Rate Zone Durations") {
if zoneDurations.isEmpty {
Text(statusMessage)
.foregroundStyle(.secondary)
} else {
ForEach(zoneDurations, id: \.zone.index) { zoneDuration in
HStack {
VStack(alignment: .leading) {
Text("Zone \(zoneDuration.zone.index + 1)")
.font(.headline)
let minBPM = zoneDuration.zone.minimumQuantity?.doubleValue(for: .count().unitDivided(by: .minute()))
let maxBPM = zoneDuration.zone.maximumQuantity?.doubleValue(for: .count().unitDivided(by: .minute()))
if let min = minBPM, let max = maxBPM {
Text("\(Int(min))ā\(Int(max)) bpm")
.font(.caption)
.foregroundStyle(.secondary)
} else if let min = minBPM {
Text("> \(Int(min)) bpm")
.font(.caption)
.foregroundStyle(.secondary)
}
}
Spacer()
Text(formatDuration(zoneDuration.duration))
.monospacedDigit()
}
}
}
}
}
.navigationTitle("Zone Summary")
.toolbar {
Button("Load Latest") { Task { await loadLatestWorkoutZones() } }
}
}
}
private func formatDuration(_ interval: TimeInterval) -> String {
let minutes = Int(interval) / 60
let seconds = Int(interval) % 60
return String(format: "%d:%02d", minutes, seconds)
}
private func loadLatestWorkoutZones() async {
let typesToRead: Set<HKObjectType> = [
HKObjectType.workoutType(),
HKObjectType.quantityType(forIdentifier: .heartRate)!
]
guard (try? await healthStore.requestAuthorization(toShare: [], read: typesToRead)) != nil else {
statusMessage = "Authorization failed"
return
}
let workoutType = HKObjectType.workoutType()
let sort = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let query = HKSampleQuery(sampleType: workoutType, predicate: nil, limit: 1, sortDescriptors: [sort]) { _, samples, _ in
guard let workout = samples?.first as? HKWorkout else {
DispatchQueue.main.async { statusMessage = "No workouts found" }
return
}
let hrType = HKQuantityType(.heartRate)
if let zoneGroup = workout.zoneGroupsByType[hrType] {
DispatchQueue.main.async {
self.zoneDurations = zoneGroup.zoneDurations
if zoneDurations.isEmpty { statusMessage = "No zone data in latest workout" }
}
} else {
DispatchQueue.main.async { statusMessage = "No heart rate zones recorded" }
}
}
healthStore.execute(query)
}
}
#Preview {
WorkoutZoneSummaryView()
}iOS 27 adds sectioned queries, codable model attributes, ResultsObserver for non-SwiftUI change observation, and HistoryObserver for reacting to persistent history changes in SwiftData.
USDKit is a new first-party Swift framework introduced in iOS/macOS 27 that brings native USD scene creation, composition, modification, and export capabilities to Apple platform apps, with deep RealityKit and Spatial Preview integration.
LiveCommunicationKit is the modern replacement for CXProvider that delivers rich, native conversation UIs integrated with the Lock Screen, Dynamic Island, Phone app Recents, and Siri. It provides a unified lifecycle model for audio and video conversations with a single delegate-driven action pipeline.
Custom zone configurations must be added to HKWorkoutBuilder before calling beginCollection ā adding them afterwards is not allowed. When comparing time-in-zone across workouts that used different zone counts, you must normalize by zone boundaries rather than zone index, as zone 3 of 5 ā zone 3 of 7. Between 3 and 9 zones are required for custom configs.
Requires a device with HealthKit support. Heart rate zones work best with an Apple Watch paired for live HR samples. Cycling power zones require a compatible power meter or compatible accessory.
The NowPlaying framework introduces a first-class Swift API for surfacing app media in system-wide now-playing surfaces ā Lock Screen, Control Center, Dynamic Island, StandBy, CarPlay, Apple Watch, and Apple TV ā via a declarative MediaSessionRepresentable protocol. It also supports remote media sessions (for controlling external speakers/TVs) and Media Sharing Extensions for routing media to third-party devices.