How to Build a Blood Pressure Tracker App in SwiftUI
A Blood Pressure Tracker app lets users log systolic and diastolic readings over time and visualize trends with interactive charts. It's built for health-conscious users managing hypertension or monitoring cardiovascular wellness between doctor visits.
Prerequisites
- Mac with Xcode 16+
- Apple Developer Program ($99/year) — required for TestFlight and App Store
- Basic Swift/SwiftUI knowledge
- Physical iPhone for testing — HealthKit does not function in the iOS Simulator
- HealthKit capability enabled in your target's Signing & Capabilities tab
Architecture overview
SwiftData is the on-device source of truth: a single BPReading model persists all readings, and @Query drives automatic UI updates without a separate ViewModel. HealthKit sits alongside SwiftData — readings are written as paired HKCorrelation objects so they surface correctly in the native Health app. The Charts framework consumes sorted SwiftData arrays directly via a dedicated BPTrendChart view. A lightweight HealthKitManager owns authorization and HK writes, keeping that side-effect logic out of views entirely.
BPTrackerApp/ ├── Models/ │ └── BPReading.swift # SwiftData @Model ├── Views/ │ ├── BPDashboardView.swift # Main list + chart │ ├── LogBPView.swift # Input sheet │ └── BPTrendChart.swift # Charts view ├── Managers/ │ └── HealthKitManager.swift # HK auth + writes └── PrivacyInfo.xcprivacy
Step-by-step
1. Data model
Define BPReading as a SwiftData @Model so readings persist automatically on-device — no CoreData stack, no manual saves.
import SwiftData
import Foundation
@Model
final class BPReading {
var id: UUID = UUID()
var systolic: Int
var diastolic: Int
var pulse: Int?
var timestamp: Date = Date()
init(systolic: Int, diastolic: Int, pulse: Int? = nil) {
self.systolic = systolic
self.diastolic = diastolic
self.pulse = pulse
}
var category: String {
switch systolic {
case ..<120: "Normal"
case 120..<130: "Elevated"
case 130..<140: "Stage 1 Hypertension"
default: "Stage 2 Hypertension"
}
}
}
2. Core UI — dashboard view
Use @Query with a reverse sort so the latest reading is always first, and pass the same array into the trend chart without additional state.
struct BPDashboardView: View {
@Environment(\.modelContext) private var modelContext
@Query(sort: \BPReading.timestamp, order: .reverse) var readings: [BPReading]
@State private var showLogger = false
var body: some View {
NavigationStack {
List {
BPTrendChart(readings: Array(readings.prefix(30)))
.frame(height: 180)
.listRowSeparator(.hidden)
ForEach(readings) { BPReadingRow(reading: $0) }
.onDelete { offsets in
offsets.forEach { modelContext.delete(readings[$0]) }
}
}
.listStyle(.plain)
.navigationTitle("Blood Pressure")
.toolbar {
Button { showLogger = true } label: {
Image(systemName: "plus.circle.fill")
}
}
.sheet(isPresented: $showLogger) { LogBPView() }
}
}
}
3. BP logging with trend charts
Render systolic and diastolic as separate LineMark series and add a dashed RuleMark at 130 mmHg to visually flag Stage 1 hypertension.
import Charts
struct BPTrendChart: View {
let readings: [BPReading]
private var sorted: [BPReading] {
readings.sorted { $0.timestamp < $1.timestamp }
}
var body: some View {
Chart {
ForEach(sorted) { r in
LineMark(x: .value("Date", r.timestamp, unit: .day),
y: .value("Systolic", r.systolic))
.foregroundStyle(.red.gradient)
LineMark(x: .value("Date", r.timestamp, unit: .day),
y: .value("Diastolic", r.diastolic))
.foregroundStyle(.blue.gradient)
}
RuleMark(y: .value("Stage 1", 130))
.lineStyle(StrokeStyle(lineWidth: 1, dash: [5]))
.foregroundStyle(.orange.opacity(0.7))
}
.chartYScale(domain: 50...200)
.chartLegend(position: .bottom)
}
}
4. Privacy Manifest setup
HealthKit requires both a Privacy Manifest and two Info.plist usage strings — the App Store will reject your binary without them.
// 1. Add PrivacyInfo.xcprivacy (File → New → Resource → App Privacy)
// NSPrivacyAccessedAPICategoryUserDefaults → CA92.1
// 2. Info.plist — required HealthKit usage strings:
// NSHealthShareUsageDescription → "Read past BP readings from Health."
// NSHealthUpdateUsageDescription → "Save BP readings to Apple Health."
// 3. Entitlements: com.apple.developer.healthkit = YES
// 4. Request authorization at launch (real device only):
import HealthKit
func requestHealthKitAccess() async {
guard HKHealthStore.isHealthDataAvailable() else { return }
let store = HKHealthStore()
let bpTypes: Set<HKSampleType> = [
HKQuantityType(.bloodPressureSystolic),
HKQuantityType(.bloodPressureDiastolic)
]
try? await store.requestAuthorization(toShare: bpTypes, read: bpTypes)
}
Common pitfalls
- Testing HealthKit on the Simulator.
HKHealthStore.isHealthDataAvailable()returnsfalseon Simulator and iPad (without the Health app). Always guard against it — and budget time to test exclusively on a real iPhone from day one. - Saving systolic and diastolic as separate HKQuantitySamples. Apple Health requires them bundled in an
HKCorrelationof type.bloodPressure. Individual samples are accepted silently but won't display as a blood pressure reading in the Health app. - Missing HealthKit usage strings at App Store review. Both
NSHealthShareUsageDescriptionandNSHealthUpdateUsageDescriptionmust be present in Info.plist even if your app only writes data. Reviewers flag the absence of either string as a rejection reason. - Schema migrations breaking existing user data. Adding a non-optional property to
BPReadingin a later version without a versionedSchemaMigrationPlanwill crash at launch for users upgrading from an older build. Version your schema from the start. - Describing the app as a diagnostic tool in App Store metadata. Language implying clinical diagnosis or treatment can trigger a guideline 5.1.3 (Health & Safety) rejection. Use "track," "log," and "monitor" — not "diagnose" or "detect."
Adding monetization: Subscription
Use StoreKit 2's Product API to offer a monthly or annual subscription that unlocks unlimited history exports, PDF reports, and trend analysis beyond 30 days. Define your subscription group in App Store Connect under Monetization → Subscriptions, then call Product.products(for:) at startup to fetch current pricing. Present the paywall with SubscriptionStoreView (iOS 17+) or a custom sheet — both gate features via Transaction.currentEntitlements. Wire up a Task { for await _ in Transaction.updates { … } } listener on app launch so reinstalls and family sharing restore automatically without user action.
Shipping this faster with Soarias
Soarias scaffolds the SwiftData model, wires the HealthKit entitlement and both Info.plist usage strings, drops a pre-filled PrivacyInfo.xcprivacy with the correct NSPrivacyAccessedAPITypes keys into your target, stubs out StoreKit subscription product IDs linked to your App Store Connect record, and configures fastlane lanes for TestFlight upload and App Store submission — all before you write a line of feature code.
For an intermediate project like a Blood Pressure Tracker, developers typically spend 3–5 hours on infrastructure: HealthKit entitlements, Privacy Manifest, Matchfile/Appfile setup, and screenshot automation. Soarias compresses that to under 15 minutes, leaving your full week to focus on the Charts trend view, the logging sheet UX, and the subscription paywall.
Related guides
FAQ
Do I need a paid Apple Developer account?
Yes. A free account lets you sideload to a personal device, but TestFlight distribution and App Store submission both require the $99/year Apple Developer Program membership. Because HealthKit only works on a real device, you'll need an active developer account from the first day of testing — not just at submission time.
How do I submit this to the App Store?
Archive your app in Xcode (Product → Archive), then use the Organizer to upload to App Store Connect. In ASC, complete your screenshots, privacy nutrition labels, and age rating, then submit for review. Expect 1–3 days for an initial decision. Health category apps sometimes receive additional scrutiny — ensure your metadata clearly describes personal tracking, not clinical diagnosis.
Does my Blood Pressure Tracker need FDA clearance?
A logging app that records readings manually entered by the user is generally considered a general wellness app and does not require FDA clearance. However, if your app algorithmically analyzes readings to provide a medical recommendation or automated emergency alert, it may fall under FDA's Software as a Medical Device framework. Keep your feature set focused on logging and visualization, and consult the FDA's Digital Health Center of Excellence resources if you plan to add any automated clinical guidance.
Last reviewed: 2026-05-12 by the Soarias team.