Appendix G: Body Readings (Shop Manual)

Status: Live on the App Store. GPL v3. Public repo at github.com/fluhartyml/Body-Readings. Build-Along chapter: Part VII Build-Along 06. Body Readings is a focused vitals dashboard: heart rate (HealthKit), step count (HealthKit), and live motion (CoreMotion), three reading cards composed by a single @ViewBuilder helper. Book 19 (Custom Views and Modifiers) reaches for this app's readingCard(label:content:) function as the textbook example of the generic-helper-function flavor of view extraction.

What it is

A small iPhone surface showing three live numbers: heart rate from the user's HealthKit data, step count from the same store, and a live motion summary from CoreMotion's device-motion stream. The app demonstrates two adjacent Apple frameworks back-to-back (HealthKit for stored biometrics, CoreMotion for live sensor data) and shows how the same SwiftUI card layout can hold either kind of source.

Architecture overview

Five Swift files. The whole app is a single screen with three cards plus the two store classes that feed them.

The readingCard helper (Book 19 reference)

The chapter's point: SwiftUI gives two ways to extract reusable view code — a named View struct or a @ViewBuilder function. The function form is the right reach when the extraction is shallow and the surrounding context already knows the data shape. ContentView uses the function form:

@ViewBuilder
private func readingCard<Content: View>(label: String, @ViewBuilder content: () -> Content) -> some View {
    VStack(alignment: .leading, spacing: 8) {
        Text(label)
            .font(.headline)
            .foregroundStyle(.secondary)
        content()
            .font(.system(size: 48, weight: .semibold, design: .rounded))
            .foregroundStyle(.primary)
    }
    .padding()
    .frame(maxWidth: .infinity, alignment: .leading)
    .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16))
}

Three callers in ContentView:

readingCard(label: "Heart Rate") {
    Text("\(healthStore.heartRate, specifier: "%.0f") bpm")
}
readingCard(label: "Steps Today") {
    Text("\(healthStore.steps)")
}
readingCard(label: "Live Motion") {
    Text(motionStore.summary)
}

The function takes a label and a content closure annotated @ViewBuilder. The body of each call site can be any SwiftUI expression. No state has to be threaded through the helper because each card's data lives in the closure's enclosing context. The helper is a layout, not a component — it doesn't know what numbers it's wrapping.

HealthKit pipeline

HealthStore handles the standard HealthKit handshake:

  1. At first launch, request authorization for HKObjectType.quantityType(forIdentifier: .heartRate) and .stepCount.
  2. On approval, start an anchored object query for heart rate (delivers new samples as they arrive) and a statistics query for steps (one number for "today").
  3. Publish the latest values to @Observable properties that ContentView reads.

The store handles the cases where authorization is denied or pending — the cards show placeholder text rather than crashing or showing zero.

CoreMotion pipeline

MotionStore uses CMMotionManager with a slow update rate (a few times per second — the cards don't need 60 Hz):

  1. Check isDeviceMotionAvailable.
  2. Set deviceMotionUpdateInterval.
  3. startDeviceMotionUpdates(to:) on the main queue.
  4. In the handler, compute a compact summary (gravity vector + user acceleration magnitude) and publish it to a summary string the UI shows.

The store stops updates when the view disappears, so the motion stream doesn't keep running in the background.

Build + deployment

Known gotchas

Sources