Sample CodemacOSReviewed 2026-07-21View on Apple Developer

Supporting Drag and Drop Through File Promises

At a glance

Item Summary
Purpose Import promised image files into a meme canvas and export the flattened canvas as a promised JPEG.
Architecture ImageCanvas owns editing/rendering; ImageCanvasController adapts drag/drop and promise APIs; an immutable snapshot carries deferred export data.
Main boundary The view recognizes gestures, while the controller owns filesystem, operation-queue, and error behavior.

Project structure

MemeGenerator/
├── ImageCanvas.swift
├── ImageCanvasController.swift
├── TextField.swift
├── WindowController.swift
└── AppDelegate.swift

The small sample separates reusable canvas mechanics from integration with AppKit’s file-promise protocols.

Overall architecture

Reference code

MemeGenerator/ImageCanvasController.swift:118 — the controller accepts either a promised file or a concrete URL and normalizes both into canvas updates.

        sender.enumerateDraggingItems(options: [], for: nil, classes: supportedClasses, searchOptions: searchOptions) { (draggingItem, _, _) in
            // ...
        }

Ownership and state

Ownership evidence

MemeGenerator/ImageCanvas.swift:20 — the snapshot captures everything needed for later flattening without retaining live controls.

    class SnapshotItem {
        // ...
    }

The canvas owns live image and text-overlay state. The controller owns asynchronous work and the temporary import destination. The provider retains a snapshot in userInfo, so a later write does not depend on subsequent canvas mutations.

Class and protocol design

Type Responsibility Contract
ImageCanvas Edit text overlays, draw, recognize drags, and create snapshots NSView, NSTextFieldDelegate, NSDraggingSource
ImageCanvasController Import/export policy, file I/O, progress, and errors ImageCanvasDelegate, NSFilePromiseProviderDelegate, NSToolbarDelegate
ImageCanvasDelegate Move host-dependent drag behavior out of the view App-defined @objc protocol
SnapshotItem Flatten captured drawing data into JPEG Nested immutable value holder
TextField Editable label plus serializable drawing item NSTextField

Access control

Boundary Effect and rationale
private work queue and destination URL Only the controller schedules promise I/O or chooses import storage.
private canvas overlay, text array, selection, and drag state Keeps gesture/rendering invariants inside ImageCanvas.
Weak ImageCanvasDelegate outlet Prevents the canvas from owning its controller and supports Interface Builder wiring.
Implicit internal types The executable shares them without declaring a public library API.
Immutable snapshot fields Export code can read but not mutate the captured rendering state.

MemeGenerator/ImageCanvasController.swift:27 and MemeGenerator/ImageCanvas.swift:107 show the two private ownership regions.

Logic ownership and placement

Logic Owner
Gesture threshold, text selection, and drawing ImageCanvas
Deferred flattened-image creation SnapshotItem
Promise receipt and URL normalization ImageCanvasController
File naming, queue choice, and JPEG write ImageCanvasController promise delegate
Individual text rendering TextField.DrawingItem

Design patterns

Pattern Evidence Purpose
Delegate MemeGenerator/ImageCanvas.swift:11 Keeps filesystem/pasteboard policy out of the view.
Snapshot MemeGenerator/ImageCanvas.swift:21 Freezes export inputs for deferred writing.
File promise MemeGenerator/ImageCanvasController.swift:146 Advertises output before the destination requests bytes.
Target/action MemeGenerator/WindowController.swift:28 Routes toolbar commands to the canvas controller.
Queue confinement MemeGenerator/ImageCanvasController.swift:160 Executes promise work off the main thread and UI updates on main.

Naming conventions

  • Types name concrete UI roles: ImageCanvas, ImageCanvasController, and TextField.
  • SnapshotItem and DrawingItem make the boundary between live controls and renderable data explicit.
  • Handler names state intent: handleFile, handleImage, prepareForUpdate, and writePromiseTo.

Architecture takeaways

  • Keep drag gesture mechanics in the view and file policy in a controller delegate.
  • Snapshot mutable UI before handing work to a deferred file provider.
  • Give file-promise I/O an explicit non-main queue.
  • Make the controller the single place that normalizes promised and concrete input files.

Source map

Source Role
MemeGenerator/ImageCanvas.swift:11 Canvas delegate boundary
MemeGenerator/ImageCanvas.swift:21 Immutable export snapshot
MemeGenerator/ImageCanvasController.swift:11 Drag/drop and promise adapter
MemeGenerator/ImageCanvasController.swift:165 Deferred file writer