Capstone: A Data-Driven, Tested SwiftUI App
iOS Development — Architecture & Data
Chapter 10 · Capstone: A Data-Driven, Tested SwiftUI App
Fundamentals' own TaskFlow worked, but everything lived in memory and nothing was tested. This capstone evolves it into a real, production-shaped app — persisted, networked, dependency-injected, and genuinely tested — wiring together every one of this course's own nine chapters.
| Chapter | What It Contributes |
|---|---|
| 1 — MVVM | TaskListViewModel as a real, dedicated ViewModel — Model/View/ViewModel separation throughout |
| 2 — Concurrency | @MainActor on the ViewModel, real Task {} for background sync |
| 3 — Networking | APIClient, URLRequest, APIError, real status-code checking |
| 4 — SwiftData | Task as a real @Model, @Query for live local display |
| 5 — Dependency Injection | APIClientProtocol, real initializer injection, FakeAPIClient |
| 6 — Unit Testing | Real @Test functions verifying TaskListViewModel with zero real network access |
| 7 — UI Testing & Instruments | Real .accessibilityIdentifier() on every interactive element |
| 8 — Device Capabilities | PhotosPicker for a task photo, a real local reminder notification |
| 9 — Reactive Patterns | A real AsyncStream-based task-added event feed |
File Structure
A Real, Deliberate Split: Task vs. TaskDTO
Task was Decodable; Chapter 4's own Task is a real
@Model class. Mixing both roles onto one type genuinely works in simple cases, but real apps
commonly keep them separate — a lightweight DTO (Data Transfer Object) matches the
server's own real JSON shape, while the persisted @Model stays focused purely on local
storage. This capstone makes that real, deliberate choice explicit.
The ViewModel: MVVM, Concurrency, and Injected Networking
@MainActor, Chapter 3's APIClientProtocol,
and Chapter 5's real initializer injection all land in this single, real ViewModel — the same real
class shape this course has been building toward since Chapter 1's own TaskStore refactor.
The View: Real, Live Local Data
@Query keeps the View's own display genuinely live — any insert from anywhere, including the
ViewModel's own syncWithServer(), updates tasks automatically. The ViewModel's
real job is narrower and more focused than Course 1's own capstone: syncing with the network, not owning
the displayed array directly — SwiftData's @Query already does that better.
Device Capabilities: Photo & Reminder
A Real, Live Event Feed: TaskEvents
Any part of the app can now observe every task-added event, in real time, using the exact same
for await vocabulary Chapter 9 established — a real, live activity feed with zero Combine
involved.
Testing It All
ModelConfiguration(isStoredInMemoryOnly: true) gives this test a real, working SwiftData
stack that never touches disk, and FakeAPIClient means it never touches a real network
either — the entire real sync flow, verified end to end, in milliseconds.
Hands-On Exercises
Add a real .accessibilityIdentifier("saveButton") to NewTaskView's own Save button, and write a real XCUITest confirming a newly-added task's own title appears in the list after the full add-task flow.
Write a real @Test function using a fresh, in-memory ModelContainer that inserts one Task directly via modelContext.insert(...), then confirms context.fetch(FetchDescriptor<Task>()) returns exactly one task with the expected title — with no networking involved at all.
Explain, in your own words, why splitting Task (a real @Model) and TaskDTO (a real Decodable struct) into two separate types is a genuinely better real design than making one type do both jobs, connecting your answer to Chapter 1's own MVVM separation-of-concerns principle.
Where to Go From Here
This capstone's own app still isn't ready to ship — no app icon, no signing, no real App Store submission, no CI pipeline running these very tests automatically. That's genuine iOS Development — Production & Publishing territory: provisioning, TestFlight, App Store Connect, and shipping this exact codebase to real users, the direct next course in this three-course arc.
What This Capstone Demonstrates
- MVVM (Ch1),
@MainActorconcurrency (Ch2), and injected networking viaAPIClientProtocol(Ch3, Ch5) in one real ViewModel - Real SwiftData persistence (
@Model,@Query) deliberately kept separate from a network-facingTaskDTO(Ch4) - Real, fast, isolated tests using
FakeAPIClientand an in-memoryModelConfiguration— no real network, no real disk (Ch5, Ch6) - Real accessibility identifiers ready for XCUITest (Ch7)
PhotosPickerand a real local reminder notification (Ch8)- A real, live
AsyncStream-based event feed, using this course's own consistent async vocabulary throughout (Ch9)