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
flowchart LR
External["Finder / other app"] --> Receiver["NSFilePromiseReceiver or URL"]
Receiver --> Controller["ImageCanvasController"]
Controller --> Canvas["ImageCanvas"]
Canvas --> Text["TextField overlays"]
Canvas --> Snapshot["SnapshotItem"]
Snapshot --> Provider["NSFilePromiseProvider"]
Provider --> External
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
classDiagram
ImageCanvasController *-- ImageCanvas : outlet
ImageCanvasController *-- OperationQueue : workQueue
ImageCanvas *-- NSTextField : overlays
ImageCanvas *-- NSImage : base image
ImageCanvas ..> SnapshotItem : creates immutable snapshot
NSFilePromiseProvider o-- SnapshotItem : userInfo
ImageCanvas --> ImageCanvasDelegate : weak outlet
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 |