Sample CodeiOS, iPadOS, Mac CatalystReviewed 2026-07-21View on Apple Developer

Adopting drag and drop in a custom view

At a glance

Item Summary
Purpose Makes an image view a drag source and the surrounding custom view a validated image drop destination.
App architecture One view controller owns the image UI; separate extensions adapt it to drag-source and drop-destination delegate protocols.
Main patterns Framework delegates, extension-based decomposition, item-provider transfer object, and local-object fast path.
Project style Minimal storyboard target with one controller split into core, drag, and drop files.

Project structure

CustomViewDragAndDrop/
├── AppDelegate.swift
├── ViewController.swift
├── ViewController+Drag.swift
├── ViewController+Drop.swift
└── Base.lproj/Main.storyboard

Structure observations

  • The files split protocol conformances, not ownership: all state still belongs to one controller.
  • Cross-process representation (NSItemProvider) and in-process representation (localObject) are created together at the drag boundary.

Overall architecture

Reference code

CustomViewDragAndDrop/ViewController.swift:22 — setup attaches interactions to the views that own their lifetimes.

override func viewDidLoad() {
    super.viewDidLoad()
    imageView.isUserInteractionEnabled = true

    let dragInteraction = UIDragInteraction(delegate: self)
    imageView.addInteraction(dragInteraction)

    let dropInteraction = UIDropInteraction(delegate: self)
    view.addInteraction(dropInteraction)
}

Interpretation

The controller is both adapter and UI state owner. UIKit drives the delegates; the drag side publishes an image representation, and the drop side validates location/type before asynchronously assigning the result.

Ownership and state

Ownership evidence

CustomViewDragAndDrop/ViewController+Drag.swift:17 — each drag item owns a portable provider and also carries the same image for local use/preview.

func dragInteraction(_ interaction: UIDragInteraction,
                     itemsForBeginning session: UIDragSession) -> [UIDragItem] {
    guard let image = imageView.image else { return [] }
    let provider = NSItemProvider(object: image)
    let item = UIDragItem(itemProvider: provider)
    item.localObject = image
    return [item]
}
Owner Object or state Relationship Mutation authority
Storyboard/controller Image view outlet Receives reference to hierarchy-owned view Controller/delegate callbacks
Image view/root view Drag/drop interactions Retain after addInteraction UIKit interaction lifecycle
Drag item Item provider and optional local image Retains for session Drag creation; UIKit transports
Controller Current displayed image and border feedback Mutates view state Drop lifecycle callbacks

Class and protocol design

Type or protocol Responsibility Depends on
ViewController Configure views/interactions and own displayed image UIKit view hierarchy
UIDragInteractionDelegate extension Produce transfer item and targeted lift preview UIImage, NSItemProvider
UIDropInteractionDelegate extension Validate type/count/location, propose copy/move, load image, update feedback UIDropSession

No app-defined protocol is needed because one concrete controller implements both UIKit boundaries.

Access control

Symbol Access Verified effect Likely rationale
Controller and outlet implicit internal Storyboard and target files can access them. Sample target integration.
Delegate methods and updateLayers implicit internal Same-module callers could invoke them; UIKit dispatches delegate methods. Conformance split across files requires target visibility under this organization.
Drag/drop temporary values lexical locals Transfer/preview objects cannot be mutated elsewhere before return. Short-lived session construction.
No explicit private / fileprivate verified absence Source establishes no lexical helper boundary. Simplicity; not required by the framework.

Reference code

CustomViewDragAndDrop/ViewController+Drop.swift:91 — drop-target highlighting is a controller helper shared only by drop callbacks in that extension file.

func updateLayers(forDropLocation dropLocation: CGPoint) {
    if imageView.frame.contains(dropLocation) {
        view.layer.borderWidth = 0
        imageView.layer.borderWidth = 2
    } else if view.frame.contains(dropLocation) {
        view.layer.borderWidth = 5
        imageView.layer.borderWidth = 0
    } else {
        view.layer.borderWidth = 0
        imageView.layer.borderWidth = 0
    }
}

Logic ownership and placement

Logic Owning type or file Placement rationale
Interaction attachment ViewController.swift Core view lifecycle.
Transfer representation and lift preview ViewController+Drag.swift Drag delegate boundary.
Acceptance, proposal, loading, highlight ViewController+Drop.swift Drop delegate boundary and UI mutation.

Design patterns

Pattern Source evidence Purpose or tradeoff
Framework delegate CustomViewDragAndDrop/ViewController+Drag.swift:10 UIKit pulls data and lifecycle decisions from the owner.
Extension-based decomposition CustomViewDragAndDrop/ViewController+Drop.swift:12 Keeps source/destination APIs readable while sharing controller state.
Transfer object CustomViewDragAndDrop/ViewController+Drag.swift:20 NSItemProvider supports process-independent image delivery.
Local fast path CustomViewDragAndDrop/ViewController+Drag.swift:22 Avoids decoding for same-app preview/coordination.

Naming conventions

  • +Drag and +Drop filenames name protocol-bound responsibilities.
  • Delegate methods retain UIKit terminology; the one helper names its visible effect, updateLayers.
  • dragInteraction/dropInteraction variables mirror framework types and lifecycle direction.

Architecture takeaways

  • Attach interactions once, then keep session decisions in framework delegate conformances.
  • Publish a transferable item-provider representation even when a local object is available.
  • Validate type, cardinality, and location before proposing a drop operation.
  • Splitting conformances across files does not create separate owners.

Source map

Source file Relevant symbols
CustomViewDragAndDrop/ViewController.swift View and interaction setup
CustomViewDragAndDrop/ViewController+Drag.swift Drag item and targeted preview
CustomViewDragAndDrop/ViewController+Drop.swift Drop validation, proposal, loading, feedback
CustomViewDragAndDrop/AppDelegate.swift Legacy application/window lifecycle