A production-quality iOS app for discovering movies and tracking your watchlist, favorites, watched history, personal ratings, and reviews — with an offline-first statistics dashboard. Built with SwiftUI, SwiftData, and modern Swift concurrency.
Portfolio project demonstrating clean MVVM architecture, dependency injection, and a fully testable, layered codebase.
- 🔎 Search TMDB with debounced input and stale-request cancellation
- 🏠 Home grid of popular movies with pull-to-refresh
- 🎞 Rich detail screen with progressive loading (instant base + async enrichment)
- 🔖 Watchlist, ❤️ Favorites, ✅ Watched history (one movie, many states)
- ⭐️ Personal ratings (half-star, drag-to-rate) and ✍️ reviews
- 📊 Statistics dashboard — counts, average rating, rating histogram, top genres, and watched-by-decade charts (Swift Charts)
- 🔀 Sort & filter saved lists (by rating, title, date, TMDB score)
- 🌓 Dark Mode, ♿️ Dynamic Type & Reduce Motion support
- 📴 Offline support — saved data and stats work with no network
- 🎇 Zoom transitions, skeleton loaders, and haptics
| Home | Detail | Statistics |
|---|---|---|
| tbd | tbd | tbd |
- Swift 6 (strict concurrency)
- SwiftUI (iOS 18+)
- SwiftData for local persistence
- Swift Charts for the dashboard
- URLSession + async/await networking
- Swift Testing for unit tests
- MVVM + repository/service layers + dependency injection
Two decoupled stacks meet at the ViewModel:
- Networking — a generic
APIClientexecutes declarativeEndpoints and decodes into immutableSendableDTOs (Movie,MovieDetail). - Persistence — a
SavedMovie@Modelstores a snapshot of each saved movie (offline-capable);@Querydrives reactive reads, aSavedMovieRepositoryowns all writes.
ViewModels are @Observable, @MainActor, and expose a finite-state enum
(idle / loading / loaded / empty / failed) that the view renders with a single
switch. Services and clients are protocols, so tests inject mocks.
See ARCHITECTURE.md for the full diagram and rationale.
MovieTracker/
├── App/ # @main entry, DI wiring, .modelContainer
├── Core/
│ ├── Navigation/ # AppTab, AppRouter, RootTabView, movieDestination
│ ├── Networking/ # APIClient, Endpoint, APIError, Models (DTOs)
│ ├── Services/ # MovieService (+ Mock)
│ ├── Persistence/ # SavedMovie, SavedMovieRepository, filtering
│ ├── Components/ # Reusable views (posters, rows, stars, skeletons)
│ ├── Theme/ # AppTheme
│ └── Utilities/ # GenreCatalog
├── Features/ # Home, Search, MovieDetail, Watchlist,
│ # Statistics, Profile, Shared (each: Views/ViewModels)
├── Resources/ # Assets, Secrets.xcconfig (git-ignored)
└── MovieTrackerTests/ # ViewModel, repository, query & calculator tests
⚠️ This repository contains the Swift source files and folder structure. It does not include an.xcodeproj(Xcode generates that). Follow the steps below on a Mac to assemble the project.
- macOS with Xcode 16+ (iOS 18 SDK)
- A free TMDB account
- Xcode ▸ New Project ▸ iOS App. Name it
MovieTracker, Interface: SwiftUI, Storage: SwiftData, Language: Swift. - Delete the generated
ContentView.swift(and the default@mainApp file — we provide our own inApp/MovieTrackerApp.swift). - Drag the
App,Core,Features, andResourcesfolders into the project navigator ("Copy items if needed", "Create groups"). - Add the
MovieTrackerTestsfiles to the test target (create a Unit Testing Bundle target if the project doesn't have one).
TMDB ▸ Settings ▸ API ▸ API Read Access Token (v4 auth).
Copy Resources/Secrets.xcconfig.example → Resources/Secrets.xcconfig and paste
your token:
TMDB_ACCESS_TOKEN = your_read_access_token_here
Then:
- Project ▸ Info ▸ Configurations → set
Secrets.xcconfigfor Debug & Release - Info.plist → add key
TMDB_ACCESS_TOKENwith value$(TMDB_ACCESS_TOKEN) - Confirm
Secrets.xcconfigis git-ignored (it is, in.gitignore)
Select a simulator and press ⌘R.
⌘U, or:
xcodebuild test -scheme MovieTracker -destination 'platform=iOS Simulator,name=iPhone 16'- ViewModel state machines (mock service + actor-based sequence mock)
- Repository behavior against an in-memory SwiftData store
- Composed query predicates against a real store
- Pure statistics calculations
See ROADMAP.md — pagination, genre-filter relationship, versioned
schema migrations, URLProtocol networking tests, widgets, notifications & deep
linking.
This product uses the TMDB API but is not endorsed or certified by TMDB.
Movie data and images © The Movie Database. (Attribution is shown in-app on the
Profile screen; add the TMDB logo to Assets before shipping.)
MIT — see LICENSE (add your preferred license file).