Sample Code

Supporting Drag and Drop Through File Promises

Receive file promises to support dragged files from other apps and provide file promises to support pasteboard operations of your app’s custom file types.

Download

Overview

Drag and drop on macOS uses an NSPasteboard that is separate from the normal copy-and-paste clipboard. When dragging content that isn’t yet a file on disk at the time dragging began, apps may put a file promise onto the pasteboard instead of a file URL to avoid blocking the main thread. File promises are different from URLs because they can be read and written asynchronously at a later time, on a background queue separate from the main queue. macOS Sierra introduced modern APIs to handle file promises.

Preview the Sample App

This sample code project is a meme generator on macOS 10.14. It allows you to combine an image and text to produce a composite meme image that can be exported as a JPEG. The sample shows you how to support both file URLs and file promises when accepting images from Mail, Safari, Photos, and other apps that support drag and drop. It also demonstrates how to provide file promises to other apps.

To see this sample in action, build and run the project, then drag an image from another app or location, such as Finder, Mail, or Safari, into the app window. Dragging the image into the window imports it into a form that the sample app can parse and consume. Add text to the image by clicking on the text button in the upper-right corner and typing. Reposition the text box by dragging and dropping it within the app window. Dragging the composite image outside the app window exports it as a JPEG.

Support Image Import by Accepting File Promises

When setting up the view supporting drag and drop, register drag types provided by NSFilePromiseReceiver. This allows the view to accept file promises that handle dragged images from Safari or Mail. The sample does this in the first view controller’s viewDidLoad.

view.registerForDraggedTypes(NSFilePromiseReceiver.readableDraggedTypes.map { NSPasteboard.PasteboardType($0) })

Handle file promises before handling URLs, since the file promise generally represents the higher-quality image and should take precedence when both types are supported. Provide a background operation queue so the read and write operation doesn’t block the main thread. Until the file promise is fulfilled, show a spinner or loading indicator to give the user immediate feedback that the app is processing the drop.

case let filePromiseReceiver as NSFilePromiseReceiver:
    self.prepareForUpdate()
    filePromiseReceiver.receivePromisedFiles(atDestination: self.destinationURL, options: [:],
                                             operationQueue: self.workQueue) { (fileURL, error) in
        if let error = error {
            self.handleError(error)
        } else {
            self.handleFile(at: fileURL)
        }
    }

Continue to handle file URLs, in case the app from which you are dragging the image doesn’t provide file promises.

case let fileURL as URL:
    self.handleFile(at: fileURL)

Support Image Export by Providing File Promises

To write images that the app creates into formats that other apps like Safari, Mail, and Finder can consume, write an NSFilePromiseProvider instance to the dragging pasteboard and conform to NSFilePromiseProviderDelegate by implementing three delegate methods.

Use the first method to provide the title of the file. This sample uses a hard-coded string for simplicity, but depending on your use case, you should take the fileType parameter into account.

func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
    return "WWDC18.jpg"
}

Provide a background operation queue in operationQueue(for:) so the file reading and writing happen without blocking the app’s UI. This method is optional, but defaulting to the main queue can block your app’s UI for writing large files to disk. When possible, provide a background queue.

func operationQueue(for filePromiseProvider: NSFilePromiseProvider) -> OperationQueue {
    return workQueue
}

The third delegate method performs the actual writing of the file to disk when it is time to fulfill the file promise. Add custom logic necessary to transform the image from your app into a file format that other apps are likely to understand, such as the JPEG format that this sample uses.

func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, writePromiseTo url: URL, completionHandler: @escaping (Error?) -> Void) {
    do {
        if let snapshot = filePromiseProvider.userInfo as? ImageCanvas.SnapshotItem {
            try snapshot.jpegRepresentation?.write(to: url)
        } else {
            throw RuntimeError.unavailableSnapshot
        }
        completionHandler(nil)
    } catch let error {
        completionHandler(error)
    }
}

See Also

File Promises

class NSFilePromiseProvider

An object that provides a promise for the pasteboard.

protocol NSFilePromiseProviderDelegate

A set of methods that provides the name of the promised file and writes the file to the destination directory when the file promise is fulfilled.

class NSFilePromiseReceiver

An object that receives a file promise from the pasteboard.