Appendix N: IntelligenceDemo (Shop Manual)

Status: Live as a proof-of-concept reference app. GPL v3. Public repo at github.com/fluhartyml/IntelligenceDemo. Build-Along chapter: Part VII Build-Along 08. IntelligenceDemo is the book's smallest possible OCR utility: two Swift files, three imports, one screen. Pick a photo, the Vision framework extracts the text, the text appears with system text-selection enabled. Books 06, 12, 14, and 18 all reach for this app as their PhotosPicker / Vision / clipboard / inline-error-feedback example. ScanKeeper (Appendix U) is the production evolution of the same idea; this demo is the chapter-teaching baseline.

What it is

One screen. The user taps PhotosPicker, picks a photo from their library, and the Vision framework runs VNRecognizeTextRequest against the image. The recognized text appears below the image with .textSelection(.enabled), which gives the user long-press / select-all / copy without any custom menu code. On recognition failure, an inline error string ("Recognition failed: ...") appears in the same text slot. That's the entire app.

Architecture overview

Two files. The smallest scale anywhere in the roster.

The flow

  1. User taps the PhotosPicker bound to pickedItem.
  2. On selection, the onChange(of: pickedItem) handler calls load(item:), which asks the PhotosPickerItem for its Transferable data and decodes it into a UIImage.
  3. The loaded UIImage is shown above the result area; isProcessing flips on.
  4. recognizeText(in: image) wraps a VNImageRequestHandler.perform(...) call in do / catch. On success, the recognized strings get joined into extractedText; on a thrown error, extractedText becomes "Recognition failed: \(error.localizedDescription)".
  5. The result Text view has .textSelection(.enabled) — the user can long-press, select, and copy with no UIPasteboard code from the app.

Why it's so small (the teaching point)

The whole app is two files because three Apple-provided pieces do everything:

The chapter's point: the smallest possible OCR utility doesn't need a state machine, a service layer, or a manager class. It needs four @State properties and two async methods.

The inline-error pattern (Book 18 reference)

Book 18 (Error Handling and Result Type) reaches for this app's recognizeText(in:) as the textbook "the error IS the result" example. The method signature is async -> String, not async throws or async -> Result. On failure, the returned string is the error message prefixed with "Recognition failed: ":

private func recognizeText(in image: UIImage) async -> String {
    do {
        // ... VNImageRequestHandler.perform(...) ...
        return recognized.joined(separator: "\n")
    } catch {
        return "Recognition failed: \(error.localizedDescription)"
    }
}

The error surface is inline with the result — no alert, no separate error state machine — because for a one-photo OCR utility, the error IS the result. The user wants to see what happened in the same place the text would have appeared. Book 18 contrasts this with CryoKit's structured-error approach (Appendix B); the comparison clarifies when each pattern is the right reach.

Permissions posture

None. PhotosPicker mediates — the system shows the picker, the app receives only the chosen item. NSPhotoLibraryUsageDescription is not required and not provided. The app cannot read the user's full photo library; it can only act on what the user explicitly picks.

Build + deployment

Relationship to Snap & ScanKeeper

Per the 2026-05-05 decision documented in project memory, IntelligenceDemo and Snap & ScanKeeper stay separate. The two apps demonstrate the same concept (turn an image into text / a saved artifact) at two different scales:

Reading them back-to-back is the case study: how a proof-of-concept grows into a shipped product. The chapter teaches on the demo; the source-tour follow-up walks the production version.

Known gotchas

Sources