NSFilePromiseProviderDelegate methods are never called

Recently, I decided to try to experiment with NSFilePromiseProvider, in order to replace some legacy code that implements drag-and-drop from an outline view to the Finder using the old-fashioned drag-and-drop methods. The process seems simple enough; just have the outline view delegate’s -outlineView:pasteboardWriterForItem: method return an NSFilePromiseProvider. However, for some reason, when I drag something from the outline view to the Finder, the drag-and-drop system seems to completely ignore my NSFilePromiseProviderDelegate methods, and calls the old-fashioned -outlineView:namesOfPromisedFilesDroppedAtDestination:forDraggedItems: method instead. If it's not there, it throws an exception. What gives?

I've reduced it down to a base-bones example, which you can see below:


import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    @IBOutlet var outlineView: NSOutlineView?
  
    class Item: NSObject {
        @objc let name: String
        @objc let children: [Item]
      
        init(name: String, children: [Item] = []) {
            self.name = name
            self.children = children
            super.init()
        }
    }
  
    @IBOutlet weak var window: NSWindow!
    dynamic var items = [Item(name: "Foo"), Item(name: "Bar"), Item(name: "Baz", children: [Item(name: "Qux")])]
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        self.outlineView?.setDraggingSourceOperationMask(.copy, forLocal: false)
    }
}
extension AppDelegate: NSOutlineViewDataSource {
    func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
        return NSFilePromiseProvider(fileType: kUTTypePlainText as String, delegate: self)
    }
  
    // If this method doesn't exist, an exception is thrown on drag
    func outlineView(_ outlineView: NSOutlineView, namesOfPromisedFilesDroppedAtDestination dropDestination: URL, forDraggedItems items: [Any]) -> [String] {
        print("Why does this get called instead of my NSFilePromiseProviderDelegate methods?!")
      
        try! "foo".write(to: dropDestination.appendingPathComponent("foo.txt"), atomically: true, encoding: .utf8)
        return ["foo.txt"]
    }
}
extension AppDelegate: NSFilePromiseProviderDelegate {
    func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
        print("fileNameForType: got called with type \(fileType)") // is never logged
        return "foo.txt"
    }
  
    func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, writePromiseTo url: URL, completionHandler: @escaping (Error?) -> Void) {
        print("writePromiseTo: got called with URL \(url)") // is never logged
        do {
            try "foo".write(to: url, atomically: true, encoding: .utf8)
            completionHandler(nil)
        } catch {
            completionHandler(error)
        }
    }
}


I mean, it works, but it works using the older system. I want it to use the modern system. Why won’t it?

Replies

You need to use setDraggingSourceOperationMask:forLocal: to set an approriate mask for non-local drags. By default it's NSDragOperationNone which is why nothing is happening.

Having the same problem. Was already setting setDraggingSourceOperationMask:forLocal: but still doesn't work: the -[filePromiseProvider:writePromiseToURL:...] method isn't being called. This started happening with the most recent macOS Big Sur Beta, must be a regression?