Appendix S: QuickNote (Shop Manual)
Status: Live on the App Store. GPL v3. Public repo at github.com/fluhartyml/QuickNote. Build-Along chapter: Part VII Build-Along 02. QuickNote is a small, focused note-taking app: one note title, a free-form body, a date, and a photo grid. SwiftData persists the notes; a home-screen widget mirrors the three most-recent. Book 09 (Text and TextField) and Book 16 (Extensions and Packages) both reach for this app as their primary Live Reference.
What it is
An iPhone-first quick-capture app for thoughts that need to land somewhere before they're forgotten. The note model is intentionally minimal: title, body, date, optional photos. No tags, no folders, no nested hierarchy — the app's job is to make capture fast, not to host an organizational system. SwiftData stores everything. A WidgetKit extension surfaces the most recent notes on the home screen without launching the full app.
Architecture overview
SwiftUI + SwiftData host app with a sibling WidgetKit extension target. The two targets share data through an App Group container.
Host app file roles:
- App entry:
QuickNoteApp.swift—@mainscene with the sharedmodelContainer. - Root and detail:
ContentView.swift(notes index with@Queryagainst the SwiftData store),NoteDetailView.swift(per-note edit surface withTextFieldtitle,TextEditorbody,DatePickerdate, photo grid). - Data model:
Note.swift— SwiftData@Modelwith title, body, date, and photo data. - Auxiliary surfaces:
AboutView.swift,ContactDeveloperView.swift(mail composer for feedback). - Under the Hood:
UnderTheHoodView.swift+UnderTheHoodContent.swift(in-app source mirror). - Lexicon Quick-Define:
LexiconContent.swift,LexiconSheet.swift,IdentifierTaggedSourceView.swift— same three-file pattern as Claude's Web Wrapper (Appendix J).
Widget extension file roles:
QuickNoteWidgetExtension/QuickNoteWidgetExtensionBundle.swift— the@mainwidget bundle.QuickNoteWidgetExtension/QuickNoteWidgetExtension.swift— the actual widget:Widgetconformance,TimelineProvider, and the widget's SwiftUIViewbody.QuickNoteWidgetExtension/QuickNoteWidgetExtension.entitlements— declares membership in the App Group container.
The data model
A single SwiftData @Model covers everything:
@Model
final class Note {
var title: String
var body: String
var date: Date
var photoData: [Data] // small inline thumbnails; full-size images on disk
init(title: String = "", body: String = "", date: Date = .now, photoData: [Data] = []) {
self.title = title
self.body = body
self.date = date
self.photoData = photoData
}
}
The ModelContainer is wired in QuickNoteApp.swift with the App Group container as its persistence location, so the widget can read the same store the host app writes to.
Input surface (Book 09 / Book 10 reference)
The Note Detail view is one of the cleanest production examples of TextField + TextEditor together:
TextFieldfor the note title — single line, large semantic font, focused on first launch of a new note so the user can start typing immediately.TextEditorfor the note body — multi-line free-form text area that grows with the keyboard, persists as a SwiftData string, and reads back identically across the app and its home-screen widget.DatePickerfor the note's date — defaults to today, editable for backdating.PhotosPickerfor the photo grid — the user-mediated picker pattern from PhotosUI; noNSPhotoLibraryUsageDescriptionneeded.
Widget extension (Book 16 reference)
The widget shows the three most-recent notes on the home screen. Architecture:
- App Group container. Both targets declare membership in
group.com.fluharty.QuickNote(or similar — the actual identifier lives in the entitlements file). The SwiftData store is created inside that container instead of the default sandbox, so both processes read and write the same file. - Read-only from the widget. The widget reads recent notes via
@Queryagainst the shared store but never writes back. The host app is the single writer. - Build number lockstep. The host app's
CURRENT_PROJECT_VERSIONmust equal the widget extension's. App Store submission rejects the binary if they diverge. This trips up first-time widget builders constantly — it's not documented prominently but it's enforced strictly. - Timeline provider — the widget refreshes whenever the host app writes to SwiftData.
WidgetCenter.shared.reloadAllTimelines()is called inside save handlers in the host app.
Sheets, alerts, contact
- About — standard About page from
AboutView.swift, presented as a sheet from a toolbar info button. - Contact Developer — mail composer wrapped via
MFMailComposeViewControllerbridged into SwiftUI. - Delete — swipe-to-delete on the notes list, no confirmation dialog (intentional — SwiftData undo is reliable, asking to confirm every delete is friction).
Build + deployment
- App name: QuickNote.
- Platforms: iPhone primary; iPad and Mac (Designed for iPad) inherit the layout.
- Frameworks: SwiftUI, SwiftData (host + widget), WidgetKit (widget), PhotosUI (photo picker), MessageUI (contact composer).
- App Group: shared identifier across host and widget — required for the shared SwiftData store.
- Privacy: No network. Notes stay on the device.
PhotosPickermeans no NSPhotoLibraryUsageDescription string is required.
Known gotchas
- Build number lockstep across host and widget. The two targets'
CURRENT_PROJECT_VERSIONvalues must match exactly or App Store submission fails. Bumping the host's build number means bumping the widget's in the same commit. - Widget can't write. The widget is a read-only mirror. Any "save from widget" feature would require an App Intent that runs in the host app's context, not direct SwiftData writes from the widget process.
- SwiftData inside an App Group container. The default
ModelContainerinitializer puts the store in the host app's sandbox, which the widget can't reach. The container has to be initialized with a custom URL inside the App Group container instead. Forgetting this means the widget shows an empty timeline even though the host app has notes. - Photo data lives in two places. The
photoData: [Data]array on the model holds small inline thumbnails. Full-size original photos are written to disk in the App Group container, with the model storing relative paths. This split keeps the SwiftData store small while letting the widget render thumbnails without disk fetches.
Sources
- Project source: github.com/fluhartyml/QuickNote (verified 2026-05-10).
- Build-Along chapter: Part VII Build-Along 02.
- Book 09 cross-reference:
TextField+TextEditorproduction patterns. - Book 10 cross-reference:
TextEditornote-body shape. - Book 15 cross-reference: SwiftData
@Model+@Query+modelContainerpattern (Appendix J covers the Bookmark variant; this app covers the App-Group-shared variant). - Book 16 cross-reference: widget extension with App Group container.
- Book 19 cross-reference:
LexiconSheet+IdentifierTaggedSourceView.