[002] Project / IGNITE
IGNITE
IGNITE is a native iOS app for tracking nutrition, workouts, activity, hydration, supplements, weight, and long-term progress.
I built it with SwiftUI, Core Data, HealthKit, Supabase, and OpenFoodFacts.
[002]
The idea
Why I built IGNITE
The information I wanted to track every day was split between nutrition apps, Apple Health, workout apps, and notes.
I built IGNITE to keep nutrition, activity, hydration, workouts, supplements, weight, and goals in one place. The same daily data also feeds the app's progress views.
[003]
Local data
Most data stays on the device
Meals, nutrition, hydration, supplements, weight, goals, and progress are stored on the device with Core Data.
The app only goes online when it needs food search or barcode results, subscription data, or another external service.
Existing logs and progress remain available without a network connection.
[004]
Food search
How food search works
Manual searches and barcode scans use the same lookup flow.
IGNITE checks Core Data first, then Supabase, and uses OpenFoodFacts when it still needs a match. Results are normalized and deduplicated before useful foods are saved locally.
- 01
Local first
IGNITE checks Core Data before making a network request.
- 02
Supabase
If there is no local match, the app searches its Supabase food database.
- 03
OpenFoodFacts
OpenFoodFacts is used when Supabase does not return a useful match.
- 04
Save locally
Useful results can be stored in Core Data for future searches and barcode scans.
[006]
Apple Health
HealthKit integration
IGNITE reads steps, active calories, weight, and workouts from Apple Health.
It can also write supported nutrition, hydration, weight, and workout data back to HealthKit.
HealthKit code lives in dedicated services rather than SwiftUI views. This keeps permissions, queries, writes, observers, and background updates in one place.
- Read from HealthKit
- Steps, active calories, weight, and workouts appear alongside IGNITE's other daily data.
- Write to HealthKit
- Supported nutrition, hydration, weight, and workout data can be saved to Apple Health.
- Background updates
- Observers and background delivery keep supported health data current when it changes outside IGNITE.
- Dedicated service
- SwiftUI views call a HealthKit service instead of handling permissions and queries themselves.

[007]
Progress
See progress over time
Swift Charts turns daily tracking data into trends for nutrition, activity, hydration, and weight.
Users can switch between time periods to see changes beyond a single day.
- Nutrition
- Calorie intake, net calories, macros, and protein.
- Activity
- Steps and active energy across historical periods.
- Hydration
- Daily water intake against established targets.
- Weight
- Body-weight changes and progress toward a target.
[008]
Goals
One set of goals across the app
During onboarding, IGNITE uses profile information and a weight goal to calculate calorie and macro targets with the Mifflin–St Jeor equation.
It also creates hydration, step, active-calorie, and body-weight goals.
The same values are used in daily trackers and progress views instead of being configured separately in each part of the app.
[009]
Architecture
How IGNITE is structured
SwiftUI views handle presentation, while view models manage screen state.
Repositories handle data access. Services handle HealthKit, StoreKit 2, networking, and other external APIs.
Dependencies are created centrally and passed to the parts of the app that need them.
[010]
Concurrency
Keeping the UI responsive
IGNITE uses Swift concurrency for network requests, HealthKit reads and writes, StoreKit purchases, and other asynchronous work.
That work runs outside the main UI flow and updates the interface when results are ready.
If a remote food service fails, locally stored meals, tracking, and progress continue to work.
[011]
Core Data
Storing application data
Core Data stores foods, meals, supplements, hydration, weight, goals, and related tracking records.
I added migrations as the data model changed so app updates could preserve existing data instead of rebuilding the store.
- Food
- Meals
- Supplements
- Hydration
- Weight
- Goals
- Related records
[012]
StoreKit 2
Subscriptions and premium access
StoreKit 2 loads products, verifies purchases, restores previous purchases, listens for transaction updates, and controls access to premium features.
The entitlement state is managed in one place and shared with the views that need it.
[013]
Localization
Four supported languages
IGNITE supports English, German, Spanish, and French.
Language selection and locale handling are managed centrally.
Supporting four languages also affected the layout because translated labels and navigation text can be much longer than English.
- 01EnglishEN
- 02DeutschDE
- 03EspañolES
- 04FrançaisFR
[014]
Testing
Testing the logic behind the UI
I mainly use XCTest for the parts of the app that carry business and data logic.
Tests cover Core Data repositories, food search and fallback behavior, caching, pagination, deduplication, network failures, translations, and view models.
In-memory Core Data stores and test services make those tests independent from production data and live APIs.
[015]
Tech stack
Tools and frameworks
- Language
- Swift
- UI
- SwiftUI · UIKit
- Storage
- Core Data · UserDefaults
- Apple frameworks
- HealthKit · StoreKit 2 · AVFoundation · Swift Charts
- Networking
- URLSession · REST
- Data
- Supabase · OpenFoodFacts
- Architecture
- MVVM · Repositories · Services · Dependency Injection
- Testing
- XCTest
[016]
Reflection
What I learned building IGNITE
IGNITE started as a relatively simple tracking app. As I added HealthKit, remote food data, subscriptions, and more local data, the difficult part became managing how those systems worked together.
I had to decide where each piece of data should come from, where it should be stored, and what the app should do when one source was unavailable.
That pushed me to separate SwiftUI views from persistence, networking, and platform integrations instead of putting everything directly inside the interface.
The main lesson was how much architecture starts to matter once an app grows beyond its first few features.