Get Started
Project Structure
Overview
ShipThatApp is a SwiftUI boilerplate targeting iOS 17+ and built with Swift 6. It follows an MVVM architecture using the @Observable macro, async/await for all asynchronous work, and @MainActor for UI updates. The project is organized by feature first, then by type — so everything that makes up a feature (its views, view models, models, and supporting types) lives close together, while shared infrastructure (services, utilities, navigation) is grouped by responsibility.
Understanding this layout makes it straightforward to find existing code, follow the established patterns, and drop in your own functionality without fighting the structure.
All app source lives under ShipThatApp/. Below is the real top-level layout.
Top-Level Layout
ShipThatApp/
├── ShipThatAppApp.swift # @main entry point; configures services & routing
├── AppDelegate.swift # UIKit delegate for legacy hooks (e.g. quick actions)
├── ContentView.swift # Root view; switches between auth and main flows
│
├── Views/ # SwiftUI views + view models, grouped by feature
├── Services/ # Business logic & external integrations
├── Models/ # Plain Codable / @Model data types
├── Navigation/ # Centralized, type-safe routing
├── Utils/ # Config, helpers, and extensions
├── Misc/ # Small standalone components
├── Shared/ # Reusable view modifiers
│
├── Assets.xcassets/ # Images, colors, and data assets
├── Lotties/ # Lottie animation files
└── Support/ # Support files
Heads Up!
View models live next to the views they drive (for example ChatView.swift and ChatViewModel.swift share a folder), rather than in a separate ViewModels/ directory. This keeps each feature self-contained.
Views
Path: Views/
Views are SwiftUI types with minimal logic; their state and behavior live in a sibling @Observable view model. The folder is grouped by feature.
AI
Path: Views/AI/
The AI-powered feature screens, each in its own subfolder:
Chat/— Multi-turn ChatGPT conversation UI. IncludesChatView,ChatViewModel,ChatModel,ChatRepository,ChatBubbleView, andSingleRequestView(a one-off, non-conversational request).GenImage/— DALL·E image generation, viaGenImageViewandGenImageViewModel.Vision/— Image analysis with the Vision API, viaVisionViewandVisionViewModel.Pokedex/— The flagship "Dex" scanner. Scan anything to identify it, then collect the result. This folder holds the UI and feature models:ScannerView,ScannerViewModel,PokedexHomeView,DexEntryViews, plus the supporting typesDexCategory,DexEntry(a SwiftData@Model), andScanResult.
SignIns
Path: Views/SignIns/
The authentication UI: SignIn, SignInOptions, RegistrationView, AppleButton, and SignInViewModel. These drive email/password, Magic Link, and Sign in with Apple flows through AuthManager.
Paywalls
Path: Views/Paywalls/
Three ready-made RevenueCat paywall designs — Paywall1View, Paywall2View, Paywall3View — plus the shared PaywallProductView.
Other Feature Views
Onboarding/—OnboardingView, the swipeable first-run feature tour.Settings/—SettingsView,EditProfileView, andFeedbackView.Animations/— Lottie animation showcases (Lottie1View,Lottie2View,Lottie3View).Content/—ContentScreenView, the placeholder home for your app's core content.
A handful of top-level views live directly under Views/: LandingView (the unauthenticated entry point that also handles Magic Link deep links), WelcomeScreen, and SplashScreenView.
Services
Path: Services/
Business logic and external integrations. Services are typically @Observable classes (or lightweight structs), receive dependencies through their initializers, and expose async/await APIs.
Auth
Path: Services/Auth/
AuthManager — the single source of truth for authentication, backed by Supabase (supabase-swift). Handles sessions for email/password, Magic Link, and Sign in with Apple.
APIClient
Path: Services/APIClient/
A small, type-safe networking layer used by the AI features:
APIManager— performs requests and decodes responses.Endpoints— defines the available backend routes.Models/—Endpoint,RequestMethod,RequestError, andEmptyResponse.
Heads Up!
Every AI call goes through a secure backend proxy with HMAC-signed requests, so your OpenAI keys never ship inside the app. The signing logic lives in Utils/CryptoUtils.swift, and routes are declared in Services/APIClient/Endpoints.swift.
OpenAI
Path: Services/OpenAI/
Thin service wrappers for each AI capability, grouped by type:
Chat/—ChatGPTService(multi-turn) andChatGPTSimpleService(one-off).Dalle/—DALLEServicefor image generation.Vision/—VisionServicefor general image analysis, andDexVisionServicefor the Dex scanner's structured identification (it reuses the signedvisionendpoint and parses the model's JSON into aScanResult).
Vision & Pokedex (the Dex scanner)
The Dex scanner is a hybrid of on-device and cloud intelligence, split across two services:
Services/Vision/—ImageClassifier, which wraps Apple's on-deviceVNClassifyImageRequest. It runs offline and off the main thread to produce an instant, private coarse category and confidence (LocalGuess) that seeds the UI.Services/Pokedex/—DexStore, which owns a dedicated SwiftDataModelContainer(Dex.store) so collectedDexEntrydiscoveries persist in their own store, separate from the chat history.
Other Services
Purchases/—PurchaseManager, the RevenueCat v5 integration powering the paywalls.QuickActionsManager/—QuickActionsManagerfor Home Screen quick actions.SplashScreen/—SplashScreenManagerfor splash state transitions.Settings/—SettingsManager, an@Observablestore for user preferences backed byUserDefaults.Recipe/—RecipeGeneratorManager, a sample AI feature.AI/—LocalLLMService, an on-device language model service.- Top-level
AuthService.swiftandRecipeService.swiftround out the layer.
Models
Path: Models/
Plain data types with no business logic — Codable structs for API serialization and small value types:
DALLERequest,DALLEResponse— DALL·E payloads.OnboardingFeature— a single onboarding slide.Recipe— the sample recipe model.SplashScreenStep— splash sequence steps.
Feature-specific models that are tightly coupled to one screen (such as DexEntry, DexCategory, and ScanResult) live alongside their views rather than here.
Navigation
Path: Navigation/
Centralized, type-safe routing built on the AppRouter package. AppRouterTypes.swift defines:
AppTab— the main tab bar (welcome,content,settings).AppDestination— every push destination, including URL deep-link parsing viafrom(path:fullPath:parameters:).AppSheet— sheet presentations (paywall, feedback form, edit profile).
It also declares the AppRouter (tab-based) and AppSimpleRouter (single-stack, for the unauthenticated flow) type aliases used throughout the app.
typealias AppRouter = Router<AppTab, AppDestination, AppSheet>
typealias AppSimpleRouter = SimpleRouter<AppDestination, AppSheet>
Utils
Path: Utils/
Configuration and cross-cutting helpers:
Config.swift— central, non-secret app configuration (product IDs, API base URL, timeouts, and similar constants).CryptoUtils.swift— HMAC request signing for the secure API proxy.Extensions/— focused extensions:Logger+Extensions(OSLog categories),String+Extensions,UIImage+Extensions, andView+Extensions.
Secrets
API keys and other secrets are not in Config.swift. They live in a gitignored Config.xcconfig (never in Info.plist or hardcoded in source). See the configuration docs for setup.
Misc & Shared
Misc/— small standalone components:ImagePicker(aUIViewControllerRepresentablephoto picker) andToaster(the in-app toast/banner system, used viaToast.shared.present(...)).Shared/Modifiers/— reusable view modifiers, such asLiquidGlassModifierfor the translucent "Liquid Glass" aesthetic.
Organization Principles
- Feature-based grouping. Views are grouped by feature (AI, SignIns, Paywalls), not by type. Everything for a feature sits together.
- MVVM with
@Observable. Each view has a sibling@Observableview model; views stay declarative and logic-light. - Dedicated service layer. All external integrations (Supabase, RevenueCat, OpenAI proxy, Vision) live in
Services/and are injected via initializers. - Centralized navigation. Destinations, tabs, and sheets are defined once in
Navigation/, keeping routing type-safe and deep-link-ready. - Secrets stay out of source. Configuration is split between
Config.swift(safe values) and a gitignoredConfig.xcconfig(secrets).