How to Actually Implement File Dragging From Your App on Mac
In macOS 10.12, Apple introduced a new API for dragging files, NSFilePromiseProvider
. As far as I can tell, the only documentation explaining how to use NSFilePromiseProvider
comes from the 2016 What’s New in Cocoa WWDC Video. As a result, when developers search for this information, they’re likely to only find outdated documentation on Apple’s site. This article explains how to implement file dragging using the latest APIs. Hopefully it will help someone searching for this information.
How to Create an NSFilePromiseProvider
When creating an NSFilePromiseProvider
object, you must supply a UTI which conforms to public.data
or public.directory
and implement its required delegate methods. Apple’s documentation contains a list of built-in UTI, and you can also specify your own in your app’s Info.plist.
The first delegate method you need to implement, filePromiseProvider(_:fileNameForType:)
-filePromiseProvider:fileNameForType:
is called before the drag has completed. You should return the base filename form this method. At this point, you do not know the what the full path of the file will be.
The second delegate method you need to implement, filePromiseProvider(_:writePromiseTo:completionHandler:)
-filePromiseProvider:writePromiseToURL:completionHandler:
is called as the drag finishes. The URL to write your file to is passed in. This is the full path, including the filename returned from the first delegate method.
You should perform file writing in this method, and then call the completion handler.
Here is a basic example of these delegate methods.
|
|
What to do With an NSFilePromiseProvider
When you create your NSFilePromiseProvider
object, and what you do with it, depends on what kind of view you are dragging from.
Since NSFilePromiseProvider
conforms to NSPasteboardWriting
, you can use it in most places that you can use an NSPasteboardItem
for dragging. However, even though it confirms to NSPasteboardWriting
if you write your NSFilePromiseProvider
directly to the pasteboard, it will not work properly.
From an NSTableView
You should return your NSFilePromiseProvider
from the NSTableViewDataSource
method tableView(_:pasteboardWriterForRow:)
-tableView:pasteboardWriterForRow:
. If you’re using an NSOutlineView
, you should use the corresponding method from NSOutlineViewDataSource
.
From an NSCollectionView
You should return your NSFilePromiseProvider
from the NSCollectionViewDelegate
method
collectionView(_:pasteboardWriterForItemAt:)
- (id<NSPasteboardWriting>)collectionView:(NSCollectionView *)collectionView pasteboardWriterForItemAtIndexPath:(NSIndexPath *)indexPath;
.
From Any Other NSView
You should initialize an NSDraggingItem
with your NSFilePromiseProvider
, then pass that NSDraggingItem
to a call to beginDraggingSession(with:event:source:)
-beginDraggingSessionWithItems:event:source:
. You can invoke this method in mouseDown(with:)-mouseDown: or mouseDragged(with:)-mouseDragged:. For more information, see the documentation for NSDraggingSession
.
Combining With Other Drag Types
In general, to adopt this new API, you replace NSPasteboardItems
that drag file promises with NSFilePromiseProvider
. However, NSPasteboardItem
allows you to drag multiple types of objects in a single NSDraggingItem
, while NSFilePromiseProvider
only allows you to drag files. Dragging multiple kinds of files can be useful to, for example, allow drags between panes in your app to pass around objects in memory, but allow drags from your app to the Finder to drag files.
To achieve the same with NSFilePromiseProvider
, you’ll need to subclass it and override a few methods.
First, you’ll need to override the writableTypes(for:)
-writableTypesForPasteboard:
method and return all the drag types your drag supports. You can call the superclass method to get an array of file types you’re dragging, and then add the non-file types to that array and return it.
Next, you’ll need to override the writingOptions(forType:pasteboard:)
-writingOptionsForType:pasteboard:
method and return []
0 for your non-file drag types. For file drag types, you should return the superclass method.
Finally, you’ll need to implement the pasteboardPropertyList(forType:)
-pasteboardPropertyListForType:
method and return the data you want written to the pasteboard. For file drag types, you’ll need to return the superclass method.
Here is a basic example of this technique.
|
|