Project 2 · Rubric and defend your codeswift-6.4/ios-26
Lesson 5 / 5
Unit 22 · Checklist

Project 2 · Rubric and defend your code

Note

Unit 22 · Project 2 · Rubric. Grade your build before Project 3. The defend-your-code prompts target the SwiftData machinery most people get working but can't yet explain — where the container lives, how the relationship's inverse works, why a smart view can't drift.

Project 2's real test is whether the object graph is correct and whether you understand the machinery — not just that relaunch happened to keep your data once. Score honestly.

Rubric

Area Meets bar Falls short
Persistence Data survives force-quit + relaunch, reliably Works sometimes; lost on a clean relaunch
Relationship @Relationship with inverse: + cascade delete; deleting a list removes its reminders Two unlinked properties; orphaned reminders
Container .modelContainer(for: [...]) once at the scene root, both types Attached per-view, or a type missing
Query @Query fetches + observes; counts derived, not stored Manual fetch/reload; a stored count that drifts
Smart views Today/Scheduled/Flagged derived by filtering the store Duplicated data or a separate "smart" store
Reminder fields Due date, priority, flag, notes all persist and edit A field that doesn't round-trip
Editor @Bindable form; optional-date + relationship pickers work Manual copy-back; date/list edits don't save
Search .searchable matches titles and notes No search, or title-only
Empty state ContentUnavailableView, not a blank list Blank screen when empty
Accessibility Rows announce title + done; swipe actions labeled Icon-only, unlabeled controls
Light/dark Correct in both; no color literals Only checked light

Accessibility is graded

VoiceOver must announce each row as "title, completed/not completed," the swipe actions must be reachable and named, and the editor's controls must be operable. Add a reminder, give it a due date, complete it, delete it — all by VoiceOver alone. An app you can't operate without sight is not done.

Light and dark are graded

Toggle dark mode and check the smart tiles, the section headers, the checkmarks, the empty state, and the editor. You used semantic colours (.secondary, .primary, system grouped backgrounds) and the list's own colour — all adapt — so both should be correct. Verify it; don't assume.

Note

The sneakiest Project 2 failure is a container attached in the wrong place, giving you two stores. It can look like it works within a session and then lose everything on relaunch. If persistence is flaky, audit where .modelContainer lives before touching anything else.

Defend your code

  1. Where is .modelContainer attached, and what breaks if it's on a child view instead of the scene? It's on the WindowGroup/scene so one store is shared through the environment. Per-view attachment creates separate contexts that don't see each other's data — inserts land in one, @Query reads another, and data appears to vanish.

  2. How does @Relationship(deleteRule: .cascade, inverse:) change what happens when you delete a list? The cascade deletes the list's reminders with it, so there are no orphans; the inverse: ties ReminderList.reminders and Reminder.list together so assigning one end updates the other. Explain what would go wrong with neither.

  3. When does SwiftData save? Did you call save() anywhere, and why or why not? It autosaves on the main run loop after changes, so interactive insert/delete/mutate persists without an explicit call. You'd only call try modelContext.save() to force an immediate write before an out-of-band read.

  4. Why can the Today tile and the Today view never disagree? Both derive from the same query of reminders filtered by the same rule — one source of truth, no stored count. Contrast that with maintaining a separate "smart list" you'd have to keep in sync.

  5. Why is priority stored as priorityRaw: Int with a computed priority, and why is the DatePicker bound through a hasDueDate toggle? SwiftData persists stored properties, so the enum round-trips as its raw Int; dueDate is optional but DatePicker needs a non-optional binding, so the toggle gates the picker and sets/clears the date. Explain the tradeoff each makes.

Definition of done

  • Every rubric row is green or you can explain an amber one.
  • Data reliably survives a real force-quit and relaunch (test it now).
  • You answered all five prompts without reopening the milestone lessons.
  • VoiceOver operates the whole app; both colour schemes are correct.

Then it's on to Project 3, where the data comes from the network instead of the disk.

Knowledge check

Q: Your app kept data during testing but a fresh relaunch lost it. Most likely cause? A .modelContainer misplacement creating a second, in-memory-only context — your session saw the unsaved context, but nothing reached the on-device store. Fix: one container at the scene root.

Q: You delete a list and its reminders survive as orphans in "All." What did you get wrong? The relationship's delete rule. @Relationship(deleteRule: .cascade, inverse: \Reminder.list) makes deleting a list remove its reminders too; without the cascade (or the inverse) the reminders keep pointing at a gone list and leak into unfiltered views.