Supporting Collection View Drag and Drop Through File Promises
At a glance
| Item |
Summary |
| Purpose |
Load photos into a collection view, reorder them, import external files/promises, and export promised files. |
| Architecture |
A controller owns collection state and operation queues; PhotoItem owns per-photo loading state; delegate extensions implement drag/drop and file-promise contracts. |
| Collection model |
NSCollectionViewDiffableDataSource and snapshots are the displayed ordering source. |
Project structure
CollectionViewDragDrop/
├── ViewController.swift
├── ViewController+DragAndDrop.swift
├── PhotoItem.swift
├── LoadPhotosOperation.swift
├── LoadThumbnailOperation.swift
├── PromiseFileProvider.swift
└── CollectionViewItem.swift
The controller’s normal collection setup and its large drag/drop protocol surface are separated into two files.
Overall architecture
flowchart LR
Loader["LoadPhotosOperation"] --> Photos["PhotoItem values"]
Photos --> DataSource["Diffable data source + snapshot"]
DataSource --> Collection["NSCollectionView"]
Collection --> Drag["ViewController drag/drop delegate"]
Drag --> Provider["FilePromiseProvider"]
Drag --> Receiver["NSFilePromiseReceiver"]
Provider --> Files["Finder / other apps"]
Receiver --> Photos
Reference code
CollectionViewDragDrop/ViewController.swift:83 — background discovery is converted to main-thread collection state in one place.
func loadPhotos() {
// ...
}
Ownership and state
classDiagram
ViewController *-- NSCollectionViewDiffableDataSource
ViewController *-- OperationQueue : loaderQueue
ViewController *-- OperationQueue : filePromiseQueue
NSCollectionViewDiffableDataSource o-- PhotoItem
PhotoItem *-- LoadThumbnailOperation : per load
PhotoItem --> ThumbnailDelegate : weak
FilePromiseProvider o-- Dictionary : userInfo
Ownership evidence
CollectionViewDragDrop/ViewController.swift:20 — separate queues express separate work ownership.
var loaderQueue = OperationQueue()
var filePromiseQueue: OperationQueue = {
let queue = OperationQueue()
return queue
}()
The view controller owns ordering, destination URL, progress UI, and promise I/O. The diffable data source retains displayed PhotoItem objects. Each item guards its own thumbnail load and weakly notifies the controller when derived display state is ready.
Class and protocol design
| Type |
Responsibility |
ViewController |
Collection composition, diffable state, queues, and thumbnail updates. |
ViewController drag/drop extension |
Collection delegate plus NSFilePromiseProviderDelegate. |
PhotoItem |
File URL, title, image state, and lazy thumbnail request. |
LoadPhotosOperation / LoadThumbnailOperation |
Background discovery and image derivation. |
FilePromiseProvider |
Add internal drag identity and file URL pasteboard representations. |
ThumbnailDelegate |
Notify one observer that derived state changed. |
Access control
| Boundary |
Effect and rationale |
private imageLoading |
Prevents overlapping thumbnail operations for one model object. |
private createLayout and data-source factories |
Keeps collection composition behind the controller. |
weak thumbnailDelegate |
Avoids a photo retaining the controller/data-source owner. |
public override writingOptions |
Preserves the superclass override’s visible contract; the internal subclass still caps external construction. |
Other declarations are implicit internal |
Cross-file delegate extensions can collaborate inside the app target. |
CollectionViewDragDrop/PhotoItem.swift:26 and CollectionViewDragDrop/PromiseFileProvider.swift:61 show these two distinct boundaries.
Logic ownership and placement
| Logic |
Owner |
| Collection layout and snapshot application |
ViewController |
| Drop validation, import, reorder, and export |
ViewController+DragAndDrop |
| Pasteboard representation details |
FilePromiseProvider |
| File enumeration and thumbnail generation |
Operation subclasses |
| Duplicate-load prevention |
PhotoItem |
Design patterns
| Pattern |
Evidence |
Purpose |
| MVC |
CollectionViewDragDrop/PhotoItem.swift:20, CollectionViewDragDrop/ViewController.swift:14 |
Separates photo state from AppKit coordination. |
| Operation |
CollectionViewDragDrop/LoadPhotosOperation.swift:11 |
Moves filesystem/image work off the main thread. |
| Delegate |
CollectionViewDragDrop/PhotoItem.swift:16 |
Reports thumbnail completion without model-to-view coupling. |
| File promise |
CollectionViewDragDrop/ViewController+DragAndDrop.swift:374 |
Defers file creation/copy until the drop destination requests it. |
| Diffable data source |
CollectionViewDragDrop/ViewController.swift:39 |
Makes the snapshot the collection ordering model. |
Naming conventions
- Operations use verb-object names:
LoadPhotosOperation and LoadThumbnailOperation.
- The framework adapter is named by its superclass role:
FilePromiseProvider.
ViewController+DragAndDrop clearly marks a capability extension, while PhotoItem names the displayed model.
Architecture takeaways
- Separate discovery work, thumbnail work, and promise I/O with explicit queues.
- Keep the collection ordering source authoritative during reordering.
- Put pasteboard encoding in the provider and drop policy in the controller delegate.
- Use weak model-to-controller callbacks for derived display updates.
Source map
| Source |
Role |
CollectionViewDragDrop/ViewController.swift:14 |
Collection owner |
CollectionViewDragDrop/ViewController+DragAndDrop.swift:13 |
Drag/drop adapter |
CollectionViewDragDrop/PhotoItem.swift:20 |
Photo model and thumbnail state |
CollectionViewDragDrop/PromiseFileProvider.swift:10 |
Pasteboard provider |