Capstone: A Working Small SwiftUI App

iOS Development Fundamentals

Chapter 10 · Capstone: A Working Small SwiftUI App

Nine chapters have each built one working piece in isolation — a counter, a badge, a list of tasks, a fetched quote. This capstone wires every one of them together into a single, genuinely working small app: TaskFlow — a task tracker with a real quote-of-the-day header, a task list, a detail screen, and a form for adding new tasks.

ChapterWhat It Contributes to TaskFlow
1 — Why iOS DevelopmentThe @main app struct, WindowGroup, real App/View protocol conformance throughout
2 — Swift Basics & OptionalsString interpolation; Quote? as a real optional, unwrapped with if let
3 — Control Flow, Functions & ClosuresA switch for priority labels; trailing closures for every Button
4 — Structs, Classes & Value/ReferenceTask as a value-type struct; TaskStore as a shared reference-type class
5 — SwiftUI FundamentalsModifiers styling TaskRow's priority badge
6 — State Management@State for local form fields, @Observable/@Bindable for the shared TaskStore
7 — Layout: Stacks, Lists & ScrollViewsList + ForEach over store.tasks, using Task's own Identifiable conformance
8 — Navigation & Multi-Screen AppsNavigationStack, NavigationLink(value:) + navigationDestination, .sheet() for adding a task
9 — Forms & Basic NetworkingA real Form for the new-task sheet; async/await + URLSession + .task for the quote header

File Structure

TaskFlowApp.swift — Ch1 Task.swift — Ch4, Ch7 TaskStore.swift — Ch4, Ch6 Quote.swift — Ch9 QuoteHeaderView.swift — Ch2, Ch9 TaskRow.swift — Ch3, Ch5 TaskListView.swift — Ch6, Ch7, Ch8 TaskDetailView.swift — Ch2, Ch3 NewTaskView.swift — Ch6, Ch9

The Model: Task and TaskStore

Task is a real value-type struct (Chapter 4) — cheap, safe to copy, conforming to Identifiable and Hashable for List and navigation (Chapters 7-8). TaskStore is a real, shared reference-type class, marked @Observable (Chapter 6) so every view holding it stays in sync automatically.

Task.swift
struct Task: Identifiable, Hashable { let id = UUID() var title: String var isDone = false var priority = 1 }
TaskStore.swift
@Observable final class TaskStore { var tasks: [Task] = [ Task(title: "Buy groceries", priority: 2), Task(title: "Finish report", priority: 3), Task(title: "Water plants", priority: 1) ] func add(title: String, priority: Int) { tasks.append(Task(title: title, priority: priority)) } func toggleDone(for task: Task) { guard let index = tasks.firstIndex(where: { $0.id == task.id }) else { return } tasks[index].isDone.toggle() } }

The Quote Header

Chapter 9's own fetch pattern, applied to a "quote of the day" shown above the task list — a real Quote? optional (Chapter 2), unwrapped with if let, populated by .task {}.

Quote.swift
struct Quote: Decodable { let text: String let author: String } func fetchQuote() async throws -> Quote { let url = URL(string: "https://api.example.com/quote/today")! let (data, response) = try await URLSession.shared.data(from: url) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw URLError(.badServerResponse) } return try JSONDecoder().decode(Quote.self, from: data) }
QuoteHeaderView.swift
struct QuoteHeaderView: View { @State private var quote: Quote? var body: some View { Group { if let quote { VStack(alignment: .leading, spacing: 4) { Text(quote.text).italic() Text("— \(quote.author)").font(.caption) } } else { ProgressView() } } .task { quote = try? await fetchQuote() } } }

The Task Row

A real switch statement (Chapter 3) turns a priority number into a label, and Chapter 5's own modifiers style it into a small badge.

TaskRow.swift
struct TaskRow: View { let task: Task var priorityLabel: String { switch task.priority { case 1: return "Low" case 2: return "Medium" default: return "High" } } var body: some View { HStack { Text(task.title).strikethrough(task.isDone) Spacer() Text(priorityLabel) .font(.caption) .padding(4) .background(.blue.opacity(0.2)) .clipShape(.capsule) } } }

The Task List, Navigation & Add Sheet

This is the capstone's own central piece — everything from Chapters 6-8 in one view: a shared @Bindable TaskStore, a real List with sections, value-based navigation into a detail screen, and a modal .sheet() for adding a task.

TaskListView.swift
struct TaskListView: View { @Bindable var store: TaskStore @State private var isShowingNewTask = false var body: some View { NavigationStack { List { Section { QuoteHeaderView() } Section("Tasks") { ForEach(store.tasks) { task in NavigationLink(value: task) { TaskRow(task: task) } } } } .navigationTitle("TaskFlow") .navigationDestination(for: Task.self) { task in TaskDetailView(task: task, store: store) } .toolbar { Button("Add") { isShowingNewTask = true } } .sheet(isPresented: $isShowingNewTask) { NewTaskView(store: store) } } } }
The Real Payoff of @Observable
TaskListView, TaskDetailView, and NewTaskView all hold the exact same real TaskStore instance — because it's a reference type (Chapter 4), adding a task in NewTaskView or toggling one in TaskDetailView is instantly visible back in TaskListView's own List, with no manual "refresh" step anywhere. This is the genuine, concrete reason Chapter 6 reached for a class here instead of a struct.

The Detail Screen & New Task Form

TaskDetailView.swift
struct TaskDetailView: View { let task: Task let store: TaskStore var body: some View { VStack(spacing: 16) { Text(task.title).font(.largeTitle) Button(task.isDone ? "Mark as Not Done" : "Mark as Done") { store.toggleDone(for: task) } } .padding() .navigationTitle("Task Detail") } }

NewTaskView is a real Form (Chapter 9), and closes itself via @Environment(\.dismiss) — a real, built-in SwiftUI environment value that dismisses whichever presentation (here, the .sheet()) currently owns the view.

NewTaskView.swift
struct NewTaskView: View { let store: TaskStore @Environment(\.dismiss) private var dismiss @State private var title = "" @State private var priority = 1 var body: some View { NavigationStack { Form { TextField("Title", text: $title) Stepper("Priority: \(priority)", value: $priority, in: 1...3) } .navigationTitle("New Task") .toolbar { Button("Save") { guard !title.isEmpty else { return } store.add(title: title, priority: priority) dismiss() } } } } }

Tying It Together

TaskFlowApp.swift
@main struct TaskFlowApp: App { @State private var store = TaskStore() var body: some Scene { WindowGroup { TaskListView(store: store) } } }

Running this in the Simulator (Chapter 1) shows a real, working task tracker: the quote loads in the background while the list appears immediately, tapping a task pushes its detail screen (Chapter 8), toggling "Mark as Done" updates the row instantly back on the list (thanks to the shared @Observable store), and the "Add" button presents a real form for creating a new task.

Hands-On Exercises

Exercise 1

Add a func delete(at offsets: IndexSet) method to TaskStore that removes tasks at the given offsets, and wire it into TaskListView's own ForEach via the real .onDelete(perform:) modifier, enabling real swipe-to-delete.

📄 View solution
Exercise 2

Change TaskDetailView so it also shows the priority label from TaskRow, then add a validation rule to NewTaskView's own "Save" button disabling it (using the real .disabled() modifier) whenever title is empty, rather than silently doing nothing on tap.

📄 View solution
Exercise 3

Explain, in your own words, why TaskListView, TaskDetailView, and NewTaskView all being handed the exact same TaskStore instance — rather than three separate copies — is what makes toggling a task's done state in one screen instantly visible in another, tying your answer back to Chapter 4's own value-type/reference-type distinction.

📄 View solution

Where to Go From Here

TaskFlow deliberately stays small — no persistence (tasks vanish on relaunch), no real backend behind fetchQuote(), no automated tests. Every one of those is genuine iOS Development — Architecture & Data territory: MVVM architecture, structured concurrency beyond a single .task {}, SwiftData for real local persistence, dependency injection, and unit testing with Swift Testing — the direct next course in this three-course arc.

What TaskFlow Demonstrates

  • The App/View protocol structure and Simulator workflow (Ch1)
  • Optionals, string interpolation (Ch2), switch and trailing closures (Ch3)
  • Task as a value-type struct, TaskStore as a shared reference-type class (Ch4)
  • Real SwiftUI views and modifiers styling a priority badge (Ch5)
  • @State, @Observable, and @Bindable keeping three separate screens in sync (Ch6)
  • List/ForEach over Identifiable data (Ch7)
  • NavigationStack, value-based navigation, and a modal .sheet() (Ch8)
  • A real Form, plus async/await networking via .task {} (Ch9)