---
title: "The iOS Companion App — HealthKit, Location Tracking, and Widgets"
description: "xeve for iOS brings health data, location awareness, and glanceable widgets to your personal analytics. HealthKit steps, sleep, heart rate — plus home/work detection and three widget types."
date: "2026-03-21"
app: orma
category: "building-xeve"
readingTime: "6 min read"
---

<p>The Mac app tracks what you do on your computer. The iOS app tracks everything else — your body, your movement, and your environment. Together, they give xeve the full picture of your day.</p>

<h2>Six HealthKit Data Types</h2>

<p>The iOS app queries Apple HealthKit for six data types:</p>

<ul>
<li><strong>Steps</strong> — daily step count with hourly breakdown</li>
<li><strong>Active Energy</strong> — calories burned from movement</li>
<li><strong>Resting Energy</strong> — basal metabolic calories</li>
<li><strong>Heart Rate</strong> — continuous samples from Apple Watch or paired monitors</li>
<li><strong>Sleep</strong> — sleep stages (awake, core, deep, REM) with duration</li>
<li><strong>Workouts</strong> — exercise sessions with type, duration, and energy</li>
</ul>

<p>HealthKit queries are async and surprisingly slow. Fetching a week of heart rate data (which can be thousands of samples) takes 2-3 seconds. The app batches all six queries in parallel and caches results in SwiftData for offline access. Each sample has an <code>isSynced</code> flag — the app syncs to Supabase in the background and only uploads new data.</p>

<h2>Home, Work, and Check-Ins</h2>

<p>The location system does not track GPS coordinates continuously — that would destroy battery life. Instead, it uses iOS significant location changes to detect transitions between known places.</p>

<p>You set your home and work coordinates once. The app then detects when you arrive at or leave either location and logs the transition. Everything else is a "check-in" — a named place you visited, with arrival time and duration.</p>

<p>Location data feeds into the web dashboard's Locations page, where you can see time spent at home vs. work, commute patterns, and a timeline of your movements. The correlation engine uses location data too — it can tell you whether working from home correlates with more or less productive coding sessions.</p>

<h2>Three Widget Types</h2>

<p>The app includes a WidgetKit extension with three widget types:</p>

<ul>
<li><strong>Small: Productivity Score</strong> — a single number showing your focus percentage for the day, with a color-coded ring (green/yellow/red)</li>
<li><strong>Medium: Today Overview</strong> — screen time, coding time, steps, and heart rate in a compact four-metric layout</li>
<li><strong>Lock Screen</strong> — three inline variants showing productivity score, coding time, or step count on your lock screen</li>
</ul>

<p>Widgets share data with the main app via App Groups. A SharedData module writes a JSON file to the shared container, and the widget extension reads it. The widget timeline refreshes every 15 minutes (Apple's minimum).</p>

<h2>SwiftUI + Combine Gotchas</h2>

<p>Two SwiftUI patterns worth noting for anyone building a similar app:</p>

<p><strong>Nested ObservableObjects do not propagate changes.</strong> AppState contains HealthKitManager, LocationManager, and SyncManager as nested objects. When HealthKitManager updates its published properties, the views observing AppState do not re-render. The fix: forward <code>.objectWillChange</code> events from child to parent using Combine's <code>.sink</code>.</p>

<p><strong>Numeric text transitions are free polish.</strong> Adding <code>.contentTransition(.numericText())</code> to any Text view that displays a number gives you a smooth digit-rolling animation when the value changes. It takes one line and makes the app feel significantly more polished.</p>

<h2>What Feeds the Dashboard</h2>

<p>All iOS data syncs to the same Supabase tables the web dashboard reads. Health samples go to <code>health_samples</code>, location events to <code>location_logs</code>, and daily summaries get computed by the daily rollup edge function. The web dashboard does not know or care whether the data came from an iPhone, an Apple Watch, or a manual entry — it is all the same schema.</p>
