Engineering article
Building FoodieCalc as a modular SwiftUI app
How FoodieCalc uses SPM modularization, Swift concurrency, latest SwiftUI APIs, SwiftData, WidgetKit, and an agentic workflow to stay maintainable.
FoodieCalc build notes
FoodieCalc started as a nutrition tracker, but the interesting part is not just the product idea. The interesting part is how quickly an iOS app can become hard to change if every screen, model, service, and navigation decision lives in one place.
So I am building FoodieCalc as a modular SwiftUI app from the start.
The app targets Swift 6.2 and iOS 26, uses SwiftUI as the UI layer, SwiftData for local persistence, WidgetKit for glanceable progress, VisionKit for barcode scanning, HealthKit for body-metric autofill, and Apple Foundation Models for on-device AI helpers.
SPM modularization
The project is split into Swift Package Manager modules instead of one large app target.
Base modules hold reusable infrastructure:
FCToolsowns SwiftData models, repositories, enums, storage helpers, logging, and schema setup.FCRouterowns the coordinator primitives.FCDesignSystemowns reusable UI and mascot assets.FCThemeManagerowns theme mode, accent color, gradients, and app-wide appearance tokens.FCNetworkingowns API calls.FCOnDeviceAIwraps Apple Foundation Models.
Feature modules own user-facing slices:
FCDashboardFCFoodLogFCFoodLibraryFCGoalsMakerFCGoalsDiaryFCBarcodeScannerFCUserProfileUIFCWeightTrackerFCOnboarding
That split gives each feature a clear boundary. The Dashboard can depend on the design system, tools, routing, weight tracker, user profile, and AI module, but base modules do not reach upward into features.
That rule matters. It keeps the foundation reusable and prevents the dependency graph from becoming a knot.
MVVM plus Coordinator plus Datasource
Most features follow the same shape:
SwiftUI View
-> Observable State
-> ViewModel
-> Datasource
-> Repository / Networking / SwiftData
Navigation is handled separately through coordinators. A coordinator owns the route enum for its feature, resolves destinations, and exposes a root view. The main coordinator owns the high-level app flow: onboarding, dashboard, diary, and library.
The result is a nice separation:
- Views render and bind.
- ViewModels handle UI decisions.
- Datasources fetch and map data.
- Repositories touch SwiftData.
- Coordinators handle navigation.
That sounds formal, but the goal is practical: make screens easier to reason about when the app gets bigger.
Swift concurrency as the default
FoodieCalc uses async/await instead of building feature flows around Combine.
Search is a good example. Food search performs instant local SwiftData lookups for saved foods, recipes, and meals, while API search is debounced before calling the FatSecret service. AI Quick Log is also naturally asynchronous:
Natural language input
-> Foundation Models parser
-> API lookup for each parsed item
-> SwiftData log entry save
-> Dashboard and widget refresh
The state is updated from @MainActor view models, and concurrent work is kept explicit with Task, cancellation checks, and Sendable models where useful.
This makes the code read like the user journey: parse, search, log, refresh.
Latest SwiftUI APIs
FoodieCalc leans into native SwiftUI instead of custom infrastructure where the system already has a good answer.
Some examples:
@Observablefor view state and coordinators.Tab(..., value:)with.tabViewStyle(.sidebarAdaptable)for iPhone and iPad layouts.safeAreaBar(edge: .bottom)for the Dashboard quick log bar..buttonStyle(.glassProminent)for the AI Log and Add Food actions..presentationSizing(.form)and.presentationSizing(.page)for native sheet sizing.Swift Chartsfor weight trends and target-weight rules.hoverEffect(.highlight)for iPad pointer polish.containerRelativeFramein the barcode result sheet.
Those choices keep the app feeling like iOS. They also reduce custom code.
Multilayout support
The app is not only a narrow iPhone layout.
The main tab view uses the sidebar-adaptable tab style, which gives iPad more room and lets the system present navigation in a platform-appropriate way. Dashboard content also adapts to the horizontal size class: compact layouts stack progress and streak cards, while regular layouts can place dashboard modules side by side.
That is the kind of detail that makes an app feel less like a stretched phone screen.
SwiftData foundation
FoodieCalc uses SwiftData for the core app data:
- Foods
- Recipes
- Meal templates
- Daily log entries
- User profile and goals
- Weight entries
- Cheat days
The schema is centralized in FCSchema, with a versioned schema and migration plan. That is important because nutrition apps accumulate user data. If the model changes later, existing installs should migrate instead of failing at launch.
The current code also includes a store recovery screen. If the local store cannot be opened, the app can offer a reset path instead of crashing immediately.
CloudKit-backed SwiftData sync is the other important piece. FoodieCalc’s user-created nutrition data is local-first, but it can follow the user’s personal Apple account through iCloud so their foods, logs, goals, and weight history are not trapped on one device. That keeps the app native without adding a custom account system.
WidgetKit architecture
Widgets are implemented as a separate WidgetKit extension. They do not read SwiftData directly.
Instead, the main app writes small Codable snapshots into App Groups UserDefaults:
- Daily calorie and macro progress
- Goal streak data
- Last update timestamp
The widget extension reads those snapshots and renders:
- Daily Progress widget
- Goals Streak widget
- Quick Log widget
- Lock Screen calorie ring
- Lock Screen macro summary
- Inline calorie status
The Quick Log widget deep-links back into the app using URLs like:
foodiecalc://diary/ai-quick-log
foodiecalc://diary/food-search
The main app handles those URLs and routes to the right tab through the coordinator.
On-device AI
The FCOnDeviceAI package wraps Apple Foundation Models behind small services:
FCAIFoodParserturns natural language into food names and gram quantities.FCAINutritionInsightServiceturns the day’s progress into a short dashboard insight.FCAINutritionEstimationServiceestimates nutrition for custom food creation.
The module stays independent from app features. It depends on Foundation Models and Observation, not on the rest of the app.
That keeps AI as a capability, not a dependency that leaks everywhere.
Agentic workflow
FoodieCalc also has an agentic workflow around the codebase.
The project includes feature-specific documentation for the architecture, networking, testing, UI, dashboard, food log, food library, goals, barcode scanner, theme manager, on-device AI, and widgets. Those docs are not marketing notes. They are routing instructions for working on the app safely.
That helps when using AI tools as pair programmers. Instead of asking an agent to infer the whole architecture from scratch, the repo tells it:
- where a feature lives,
- what pattern it follows,
- which modules it may import,
- how data flows,
- and what constraints matter.
This is especially useful in a modular app. Good boundaries only help if future changes respect them.
The lesson so far
The strongest technical decision in FoodieCalc is not one API. It is the combination:
- SPM modules for boundaries.
- Coordinators for navigation.
- SwiftData repositories for persistence.
- Swift concurrency for readable async flows.
- Native SwiftUI APIs for adaptive layouts.
- Widget snapshots for extension-safe data sharing.
- Agent-facing docs for repeatable development.
That foundation gives the app room to grow without turning every new feature into a rewrite.