Ratatouille – Technical Documentation
RevenueCat Shipyard 2026 · Tech Stack, Architecture & RevenueCat Implementation
Tech Stack
Language
Swift 5
UI Framework
SwiftUI (iOS 26+)
Persistence
SwiftData
AI — Recipe Import
Firebase AI Logic (Gemini 2.5 Flash)
AI — Allergen Analysis
Apple Intelligence (FoundationModels, on-device)
Monetization
RevenueCat SDK v5.58
Grocery List
EventKit (Apple Reminders)
Other Frameworks
VisionKit, CoreMotion, TipKit, PhotosUI
Architecture
The app follows an MVVM pattern with a service layer, using SwiftUI’s @Observable for reactive state management and SwiftData for persistence.
Concurrency model
The entire module defaults to @MainActor via the build setting SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor. All views and ViewModels run on the main actor by default. Services that perform background work (AI calls, network requests) are explicitly marked nonisolated.
Layer overview
Models (SwiftData) — Recipe and FamilyMember, persisted in an App Group container shared with the Share Extension. Recipe stores ingredients as Codable arrays, cached allergen analyses, and supports dynamic portion scaling via formattedQuantity(scale:).
ViewModel — A single RecipeStore (@Observable) serves as the source of truth. It manages CRUD, filtering, search, family member selection, portion coefficients, and import orchestration.
Services — Five specialized services, each with a single responsibility:
RecipeImporter— AI-powered import (URL, social media, document scan)IngredientAnalyzer— On-device allergen detection (Apple Intelligence)IngredientDatabase— Local database of 975+ mapped ingredientsShoppingListService— EventKit integration for Apple RemindersPurchaseManager— RevenueCat subscription management
Views — Organized by feature (Home, Detail, Recipe, Family, Onboarding, Paywall, Settings). Navigation uses NavigationStack with .fullScreenCover and .sheet modals. iOS 26 Liquid Glass effects are used throughout (no #available checks needed).
Data flow — Recipe import
User pastes URL → Import button
1. RecipeStore creates a PendingImport (observable state for live progress UI)
2. RecipeImporter (nonisolated) fetches HTML, extracts metadata (og:image, schema.org JSON-LD), sends to Gemini 2.5 Flash with a structured prompt
3. Gemini returns structured JSON — title, ingredients, steps, times, servings — translated to user’s language, units normalized
4. Image downloaded from extracted URL
5. Back on MainActor: Recipe model created, inserted into SwiftData, toast notification shown
6. IngredientAnalyzer runs on-device when user opens the recipe — checks local DB first, then Apple Intelligence for unknowns
Data flow — Allergen detection
Two-tier system: speed + coverage
Tier 1 — Local database: 975+ ingredients pre-mapped to allergens and diet incompatibilities. Instant lookup, no network, no AI needed. Covers common ingredients in French and English.
Tier 2 — Apple Intelligence: For unknown ingredients, on-device AI analysis via FoundationModels framework. Uses @Generable structs for type-safe structured output. Results filtered by confidence (minimum 4/5) — low-confidence results return empty analysis to avoid false positives.
Cache: Analyses are stored on the Recipe model. A hash of family restrictions (familyRestrictionsVersion) triggers re-analysis when family members or their allergens change.
Graceful degradation: If Apple Intelligence is unavailable (older device), the app relies on the local database only. No crash, no error — just slightly reduced coverage.
Share Extension
A separate app extension allows users to share recipe URLs directly from Safari, Instagram, TikTok, or any app. The extension saves the URL to a shared JSON file in the App Group container. On next app launch, RecipeStore picks up pending imports and processes them — checking import credits before consuming them.
RevenueCat Implementation
Configuration
SDK
RevenueCat iOS v5.58 (SPM)
Entitlement
Ratatouille Premium
Monthly Product
ratatouille_monthly_v1
Annual Product
ratatouille_annual_v1
Purchases.configure(withAPIKey:) is called at app launch in RecipeOrganizerApp.init(), alongside Firebase configuration.
PurchaseManager
A single @Observable class manages the entire subscription lifecycle:
- Entitlement check: reads
CustomerInfo.entitlements["Ratatouille Premium"]?.isActive— both at init and via delegate for real-time updates - Purchase flow:
purchase(_ package: Package) async → Bool— handles loading state, user cancellation, and error messages - Restore:
restorePurchases() async— for users who reinstall or switch devices - Package resolution: finds monthly/annual packages by type or product ID fallback
Free tier enforcement
AI import credits (3 free) — Tracked locally via @AppStorage("usedImportCredits"). Each successful AI import (URL, scan, or share) calls consumeImportCredit(). Premium users bypass the counter entirely. The remaining count is displayed in the toolbar and import sheets.
Family members (2 free) — Checked at add time via canAddFamilyMember(currentCount:). Returns true if premium or current count < 2.
Paywall triggers
The paywall is presented contextually — never as a cold gate, always when the user hits a real limit:
URL / Scan import
User tries to import with 0 credits remaining → paywall sheet
Add family member
User tries to add a 3rd member → paywall sheet
Share Extension import
Pending imports blocked by 0 credits → paywall with “X recipes waiting” message
Home toolbar
“X free imports” capsule (visible for free users) → tap opens paywall
Settings
“Upgrade to Plus” row with remaining import count
Onboarding
Final step of onboarding flow (skipped if already premium)
Paywall UI
The paywall view features an animated Ratatouille-themed ambilight background with cycling color schemes, a floating chef’s hat animation, and a clear Free vs. Plus comparison table. Package selection uses an expandable capsule with the annual plan pre-selected and labeled “Best Value.” The bottom section uses iOS 26 Liquid Glass effects. Footer includes restore purchase, privacy policy, and terms of service links.
Privacy & Data
All family data stays on-device. Allergen profiles, family members, and dietary restrictions are stored locally in SwiftData. No server, no analytics on family composition.
Allergen analysis runs on-device via Apple Intelligence (FoundationModels). Ingredient data never leaves the phone.
Recipe import sends HTML content to Gemini (Firebase AI Logic) for extraction — this is the only network-dependent AI call. No user data (family, allergens) is included in those requests.
Built with
SwiftUI · SwiftData · Gemini · Apple Intelligence · RevenueCat
