01 - Brain health · SwiftUI and HealthKit Live on the App Store

Cognition Daily

A free brain health app built to HIPAA standards from the first commit. Validated clinical screeners, Apple Health data, and a single Brain Health Score, all held to a privacy bar that was set before the first feature was designed.

Cognition Daily app icon
Demo

From habit to score

Logging a day of tracking, playing through a game, and watching both land in the Brain Health Score on the dashboard.

Overview

Cognition Daily helps people build and keep the everyday habits that support brain health. You track physical activity, diet, sleep, mindfulness, and social connection, play cognitive games, and take screening tools at your own pace. All of it rolls up into a single Brain Health Score with a breakdown by pillar, so the daily choices that matter show up in one place instead of across five different apps.

I am the sole developer and designer. Over four months I built every screen, every service, and every piece of the data layer. What I did not own was the medicine. A medical research team owned instrument selection, question wording, and scoring validity, and my job was to implement their work exactly rather than improve on it. That split defines the project. It is the first app I have built where the constraints came from outside the code, and where being right mattered more than being clever.

The problem

Most apps can treat privacy as a checklist item near the end. An app that reads Apple Health, asks about depression and anxiety, and stores a person’s medical history cannot. The compliance bar had to come before the architecture, because almost every default convenience in iOS development quietly breaks it.

  • Health data rules out the normal conveniences. No print debugging against real data, no caching responses to UserDefaults, no analytics SDK anywhere near a HealthKit read. Every usual shortcut for building a feature faster is off the table.
  • The clinical content is a validated instrument, not copy. Question wording, scale labels, and scoring are fixed by the research team. Paraphrasing a question to fit a screen or rounding a score to look cleaner invalidates the data the app exists to collect.
  • Deletion has to be real. A user who asks to leave has to actually be gone, locally and remotely, which is a much harder engineering problem than flipping a soft delete flag and hiding a few rows.

Approach

  1. 01

    Decide what is allowed to touch disk

    Before writing features I drew a line through the data model: credentials and session tokens go to Keychain, non-identifying preferences go to UserDefaults, and everything clinical stays server side behind the user’s session. Nothing sensitive is written to a local file, and the app ships with a data protection entitlement so anything that does land on disk is unreadable while the device is locked. Drawing that line first meant I never had to go back and audit for leaks, because nothing ever had a path to write one.

  2. 02

    Debug without ever printing the data

    Losing print debugging on the data that matters most sounds small and is not. I leaned on tests instead, sixty five suites written in Swift Testing that exercise scoring, health imports, and purging against synthetic inputs. When something broke, the fix came from reproducing it in a test with fake data, never from logging a real user’s answers to the console.

  3. 03

    Read from Apple Health, never write, never forward

    The app reads steps, exercise minutes, workouts, sleep, and mindful sessions so weekly tracking fills itself in. It writes nothing back, and the usage string says so in plain language rather than leaving users to guess. HealthKit values never cross into any third party integration. Nothing in the SDK enforces that for you, so I enforced it by hand at every integration point.

  4. 04

    Implement the instruments exactly, then get out of the way

    Every screener question, scale label, and option string is copied character for character from the validated source, apostrophes and punctuation included. Where my scoring disagreed with the reference implementation, the reference won and I changed my code. Screening results open behind a consent sheet with crisis resources, and nothing in the app uses diagnostic language, because a screener that flags a risk is not a diagnosis, and the interface is not allowed to suggest otherwise.

Technical highlights

  • Keychain only for credentials - Session tokens live in Keychain and never in UserDefaults or a plist. The launch route resolver reads Keychain state directly to decide where to send a returning user, so routing never needs a less secure copy of auth state somewhere else.
  • Real deletion, both ends - Account deletion runs a local purge alongside the server side removal, and Apple sign in users are re-authenticated before it proceeds. A pending deletion state covers the gap between the request and its completion, so the app never presents a half deleted account as if it were healthy.
  • Privacy cover on backgrounding - Sensitive screens are masked in the app switcher, so a screener in progress does not end up in a system snapshot sitting on disk. It works against SwiftUI’s normal lifecycle assumptions and took far more care than the single modifier it looks like.
  • A privacy manifest that matches reality - Ten collected data types, every one linked to the user, every one marked as not used for tracking, and zero tracking domains. Required reason declarations cover both the app’s own UserDefaults access and the system boot time clock the keystroke timing uses. I kept the manifest current as features landed instead of filling it out the week of submission.
  • Modern concurrency under isolation - Thirty six observable models and forty four files under actor or main actor isolation, with async await throughout and no completion handlers or dispatch queues anywhere. Weekly pillar scores are fetched concurrently and composed into one dashboard state, so a slow pillar does not serialize behind the others.
  • Accessibility as part of the instrument - Dynamic Type is supported across the app and OpenDyslexic is available as a font preference. Inter and Merriweather are bundled deliberately rather than substituted with system fonts, because in an app that asks people to read carefully and answer honestly, legibility is part of the measurement.

Screens

Cognition Daily dashboard with daily progress, a due screening, and the Brain Health Score

Dashboard

Due screenings, today’s progress, and the Brain Health Score in one place.

Brain Health Trend chart with toggleable sub-scores for each pillar

Brain Health Trend

The score over time, with each pillar available as its own sub-score.

Weekly trackers for diet, sleep, activity, mindfulness, and social connection

Weekly Trackers

The five pillars as weekly check-ins, with activity, sleep, and mindfulness filled in from Apple Health.

Before you begin sheet with a not-a-diagnosis notice, instrument citations, and crisis lines

Before You Begin

Every screener opens behind a notice that it is not a diagnosis, with citations and crisis lines.

GAD anxiety screener showing questions and answer options word for word from the validated instrument

Validated Screeners

Questions and answer options match the published instrument word for word.

Typing rhythm test with a passage, live timing stats, and a touchscreen caveat

Typing Rhythm

Key hold times and typing rhythm tracked over time, with an upfront note on the limits of touch timing.

What is next

Deeper Apple Health coverage, richer longitudinal views as users accumulate months of data rather than weeks, and continued work with the research team on which instruments are validated enough to ship next.

Outcome
5 Brain health pillars
5 Validated screeners
10 Declared data types
0 Tracking domains

I went in thinking compliance was a checklist I would work through before submission. It is not. It is a design constraint that reaches back to the first file you write and takes away tools you did not realize you depended on. Not being able to print a value, cache a response, or reach for an analytics SDK forced a discipline on this codebase that I would not have arrived at on my own, and the app is better built for it.

The other lesson was learning to build to a specification I was not allowed to improve. My instinct on every prior project was to fix anything that looked wrong. Here, wording I would have tightened and scoring I would have rounded were both off limits, because the value of the data depends on the instrument staying identical to the validated version. Letting go of that authorship was harder than any of the engineering, and it is the lesson I expect to matter most in the work I do next.