TechnologiesSwiftUIDrag and Drop

DropProposal struct

iOSmacOStvOSwatchOSvisionOS✓ renders

The behavior of a drop.

How it works

A DropProposal is the value a drop delegate returns to tell SwiftUI how it intends to consume an incoming drag — whether it will copy, move, link, or refuse the dragged content. Because a delegate is consulted continuously as the pointer moves over a target, the proposal lets you steer the drag's outcome before the user releases, which in turn drives the cursor badge the system shows. Reach for DropProposal whenever you implement DropDelegate and need finer control over the proposed operation than the default onDrop behavior provides.

  1. Return a proposal from dropUpdated(info:)

    The dropUpdated(info:) method of DropDelegate is called repeatedly while a drag hovers, and its return type is DropProposal?. Returning a proposal advertises how you'll handle a release at the current location; returning nil falls back to the system default. Here TextDrop returns a fresh DropProposal each time the pointer updates over the target.

  2. Choose the operation in the initializer

    DropProposal(operation:) takes a DropOperation.copy, .move, .link, .forbidden, or .cancel — that names the intended action and determines the badge drawn next to the cursor. The example proposes .copy, signaling that dropping will duplicate the dragged text rather than move it out of its source.

  3. Pair the proposal with a performDrop decision

    The proposal only states intent; performDrop(info:) does the work and returns a Bool for success. Keep the two consistent: because TextDrop proposes .copy in dropUpdated, its performDrop accepts the drop and returns true, invoking onDrop to update dropped.

  4. Attach the delegate with onDrop(of:isTargeted:delegate:)

    A DropProposal reaches the view through the delegate form of onDrop. The onDrop(of:isTargeted:delegate:) modifier wires TextDrop to the target and binds $isTargeted so you can react to hovering, while the of: argument (["public.text"]) gates which uniform type identifiers the delegate — and therefore your proposal — even sees.

Try it — Change DropProposal(operation: .copy) to DropProposal(operation: .forbidden) and watch the cursor show a no-drop badge as the proposal refuses the drag.

Example & preview

Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.

DropProposal.swift
struct DropProposalDemo: View {
    @State private var dropped = "Drop text here"
    @State private var isTargeted = false

    struct TextDrop: DropDelegate {
        let onDrop: () -> Void
        func dropUpdated(info: DropInfo) -> DropProposal? {
            DropProposal(operation: .copy)
        }
        func performDrop(info: DropInfo) -> Bool {
            onDrop()
            return true
        }
    }

    var body: some View {
        VStack(spacing: 16) {
            Text("Drag onto the box")
                .font(.headline)
            Text(dropped)
                .frame(maxWidth: .infinity, minHeight: 80)
                .background(isTargeted ? Color.blue.opacity(0.2) : Color.gray.opacity(0.15))
                .cornerRadius(12)
                .onDrop(of: ["public.text"], isTargeted: $isTargeted, delegate: TextDrop { dropped = "Dropped!" })
        }
        .padding()
    }
}
Live preview
Drag onto the box Drop text here
Drag onto the box Drop text here
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →