WWDC21 Challenge: Large Text Challenge
At a glance
| Item | Summary |
|---|---|
| Purpose | Teach Dynamic Type by letting people change layout properties and inspect exercise-specific feedback. |
| Architecture | A code-light, single-target SwiftUI challenge organized around JSON scenarios and per-exercise observable state objects. |
| Main pattern | ScenarioFeedbackProtocol gives one feedback view a common interface over different exercise models. |
| Boundary | No extension, persistence layer, network service, or reusable package. |
Project structure
LargeTextChallenge/
├── Models/ # Scenario, Texture, JSON loader
├── Views/
│ ├── Scenarios/ # ExerciseOne ... ExerciseFive
│ ├── Supporting/ # AdaptiveStack and shapes
│ └── ScenarioFeedbackView.swift
└── Extensions/
Each exercise intentionally keeps its property model and view variants together.
Overall architecture
flowchart LR
App["LargeTextChallengeApp"] --> List["ScenarioList"]
JSON["scenarios.json"] --> Loader["load<T>()"]
Loader --> Scenario["[Scenario]"]
Scenario --> List
List --> Exercise["ExerciseOne ... ExerciseFive"]
Exercise --> VM["Exercise...Properties"]
VM --> Feedback["ScenarioFeedbackView"]
Reference code
LargeTextChallenge/Views/ScenarioList.swift:29 — data keys select a concrete teaching view (abridged).
List(scenarioData) { scenario in
if scenario.exercise == .textWithButton {
NavigationLink(destination: ExerciseOne(scenario)) { ... }
} else if scenario.exercise == .textWithImage {
NavigationLink(destination: ExerciseThree(scenario)) { ... }
}
}Ownership and state
classDiagram
ScenarioList *-- Bool : isIntroPresented
ExerciseOne *-- ExerciseOneProperties : StateObject
ExerciseOneProperties o-- Scenario
ExerciseOneProperties ..|> ScenarioFeedbackProtocol
ScenarioFeedbackView o-- ScenarioFeedbackProtocol
ExerciseOneProperties *-- Bool : published choices
Ownership evidence
LargeTextChallenge/Views/Scenarios/ExerciseOne.swift:128 — the exercise creates and retains its observable state.
struct ExerciseOne: View {
let scenario: Scenario?
@StateObject private var viewModel: ExerciseOneProperties
init(_ scenario: Scenario? = nil) {
self.scenario = scenario
self._viewModel = StateObject(
wrappedValue: ExerciseOneProperties(scenario)
)
}
}The exercise model is the mutation authority for choices. JSON-backed Scenario values are immutable descriptions shared by value.
Class and protocol design
Each Exercise...Properties class adopts ObservableObject and ScenarioFeedbackProtocol. The protocol exposes only results(), so ScenarioFeedbackView accepts any exercise without knowing its individual toggles (LargeTextChallenge/Views/ScenarioFeedbackView.swift:10). Views remain value types; reusable AdaptiveStack<Content> uses a generic @ViewBuilder instead of type erasure.
Access control
| Symbol | Access | Rationale |
|---|---|---|
Exercise viewModel |
private |
Only its root view creates the StateObject (LargeTextChallenge/Views/Scenarios/ExerciseOne.swift:130). |
| Scenario fixture globals | internal default | All exercises in the single app target read them (LargeTextChallenge/Models/Data.swift:12). |
| Exercise/property types | internal default | They are target-local teaching components. |
| Public/file-private API | none | No cross-module contract exists; file-level widening is unnecessary. |
Logic ownership and placement
| Logic | Owner | Placement reason |
|---|---|---|
| Bundle decoding | Data.swift generic loader |
One small fixture-loading boundary. |
| Exercise routing | ScenarioList |
The scenario key maps directly to destination views. |
| Choice mutation and evaluation | Exercise...Properties |
Published choices and feedback calculation change together. |
| Adaptive horizontal/vertical layout | AdaptiveStack |
Reusable layout behavior is isolated from exercise content (LargeTextChallenge/Views/Supporting/AdaptiveStack.swift:17). |
Design patterns
| Pattern | Evidence | Purpose |
|---|---|---|
| Data-driven exercise catalog | Codable scenarios and enum keys | Keeps copy/feedback outside view code. |
| Strategy protocol | ScenarioFeedbackProtocol |
One result UI works for multiple exercise models. |
| Observable view model | Per-exercise property classes | Shares toggles across the menu, content, and feedback views. |
| Generic composition | AdaptiveStack<Content> |
Switches layout without duplicating child content. |
Naming conventions
- Exercise families share numbered prefixes, making related state/menu/view types easy to scan.
- Model types use nouns (
Scenario,Texture); feedback outputs addResult. - Boolean properties read as decisions (
allowsScrolling,stackVertically).
Architecture takeaways
- Keep lesson configuration immutable and user choices in an observable owner.
- A tiny protocol can unify feedback without forcing common storage.
- Generic view builders are effective protocol-oriented composition for SwiftUI layout.
- The code contains deliberate challenge variants; they are teaching states, not production fallback paths.
Source map
| Source | Role |
|---|---|
LargeTextChallenge/LargeTextChallengeApp.swift:10 |
App entry point |
LargeTextChallenge/Models/Scenario.swift:11 |
Scenario schema |
LargeTextChallenge/Models/Data.swift:17 |
Generic fixture decoder |
LargeTextChallenge/Views/ScenarioFeedbackView.swift:58 |
Shared result UI |
LargeTextChallenge/Views/Scenarios/ExerciseOne.swift:13 |
Observable strategy implementation |
LargeTextChallenge/Views/Supporting/AdaptiveStack.swift:43 |
Adaptive composition |