miniHabits
BUILD LOG · OUTLINE · 30 JAN 2026

Building a habit tracker in SwiftUI: the parts that were hard

Not the list view. The date maths, the widget writes, and the migration that ran on 40 000 rows.

build log 366 words of 2 200 planned

The screenshots of this app show a list with checkboxes, and a list with checkboxes is a weekend. That is what I thought too. The hard parts turned out to be everywhere except the view layer, and most of them are the kind of thing you only find out about after real people have used the app in timezones you do not live in.

Three of them cost weeks rather than days. Date maths, because “today” is not a fixed concept once you have users who cross midnight in a plane, travel between zones, and expect a habit logged at 01:00 to count for the previous day if they say so. Widget writes, because an interactive widget runs in a different process with a different sandbox view of your store, and getting a tick from a widget to appear in the app without a launch is not the one liner the sample code implies. And a schema migration on a store with 40 000 entries, which is fine on my phone and takes eleven seconds on a four year old device.

This post is the technical write up of those three, plus the smaller ones: streak computation that stays correct when you backfill, HealthKit observers that fire more often than you want, and the statistics screen that had to stop recomputing everything on every appear.

Code is included where it is short enough to be useful.

Outline

Status: outline. The introduction above is finished copy. Everything below is the section plan for the full draft, targeted at about 2 200 words.

  1. What was actually easy. The list, the theming and the settings, in a paragraph.
  2. Date maths. Timezones, day boundaries, custom day start, and the flight test.
  3. Widget writes. App groups, the store the widget sees, and reloading the right timeline.
  4. The 40 000 row migration. What it cost on old hardware, and the staged version that shipped.
  5. Streaks with backfill. Recomputation, caching, and the invalidation rule.
  6. HealthKit observers. Firing too often, and debouncing without losing updates.
  7. The statistics screen. Precomputing, and where the frame drops were.
  8. What I would do differently. Two architecture decisions, honestly assessed.
Try miniHabits free — 10 habits, no account.
Get it