Home > Blockchain >  Does an NSFilePromiseProviderDelegate need to be also an NSView or NSViewController?
Does an NSFilePromiseProviderDelegate need to be also an NSView or NSViewController?

Time:02-27

I am going through the AppKit tutorial Supporting Drag and Drop Through File Promises. I downloaded the demo app.

I tried to extract the NSFilePromiseProviderDelegate functionality from the ImageCanvasController class (which is an NSViewController) into a separate class. (Please see before & after code snippets below.)

Before my changes, dragging an image from the app canvas out into Finder and Apple Notes worked fine. But after my changes, nothing happens when I drag into Notes, and I get this error when I drag into Finder:

2022-02-26 23:16:52.713742 0100 MemeGenerator[31536:1975798] *** CFMessagePort: dropping corrupt reply Mach message (0b000100)

Is there any undocumented protocol conformance that I need to add to the new class? Or is there some underlying logic for which NSFilePromiseProviderDelegate only works if it's also an NSView or an NSViewController? In all the guides I found online, it is always tied to a view, but I didn't find any warning that it has to be.

Note: The reason I want to separate the promise-provider functionality from views is, this way I could provide multiple NSDraggingItem objects to beginDraggingSession. For example, when multiple items are selected, and there is a mouseDragged event on one of them, I could start a dragging session including all the selected items.

Code before

class ImageCanvasController: NSViewController, NSFilePromiseProviderDelegate, ImageCanvasDelegate, NSToolbarDelegate {
   
    ...

    /// Queue used for reading and writing file promises.
    private lazy var workQueue: OperationQueue = {
        let providerQueue = OperationQueue()
        providerQueue.qualityOfService = .userInitiated
        return providerQueue
    }()
    
    ...

    func pasteboardWriter(forImageCanvas imageCanvas: ImageCanvas) -> NSPasteboardWriting {
        let provider = NSFilePromiseProvider(fileType: kUTTypeJPEG as String, delegate: self)
        provider.userInfo = imageCanvas.snapshotItem
        return provider
    }
    
    // MARK: - NSFilePromiseProviderDelegate
    
    /// - Tag: ProvideFileName
    func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
        let droppedFileName = NSLocalizedString("DropFileTitle", comment: "")
        return droppedFileName   ".jpg"
    }
    
    /// - Tag: ProvideOperationQueue
    func operationQueue(for filePromiseProvider: NSFilePromiseProvider) -> OperationQueue {
        return workQueue
    }
    
    /// - Tag: PerformFileWriting
    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)
        }
    }
}

Code after

class CustomFilePromiseProviderDelegate: NSObject, NSFilePromiseProviderDelegate {
    /// Queue used for reading and writing file promises.
    private lazy var workQueue: OperationQueue = {
        let providerQueue = OperationQueue()
        providerQueue.qualityOfService = .userInitiated
        return providerQueue
    }()

    /// - Tag: ProvideFileName
    func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
        let droppedFileName = NSLocalizedString("DropFileTitle", comment: "")
        return droppedFileName   ".jpg"
    }

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

    /// - Tag: PerformFileWriting
    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)
        }
    }
}

class ImageCanvasController: NSViewController, ImageCanvasDelegate, NSToolbarDelegate {

    ...

    func pasteboardWriter(forImageCanvas imageCanvas: ImageCanvas) -> NSPasteboardWriting {
        let delegate = CustomFilePromiseProviderDelegate()
        let provider = NSFilePromiseProvider(fileType: kUTTypeJPEG as String, delegate: delegate)
        provider.userInfo = imageCanvas.snapshotItem
        return provider
    }
}

CodePudding user response:

Does an NSFilePromiseProviderDelegate need to be also an NSView or NSViewController?

No.

delegate is a local variable and is released at the end of pasteboardWriter(forImageCanvas:). Without a delegate the file promise provider doesn't work. Solution: keep a strong reference to the delegate.

class ImageCanvasController: NSViewController, ImageCanvasDelegate, NSToolbarDelegate {

    let delegate = CustomFilePromiseProviderDelegate()

    ...

    func pasteboardWriter(forImageCanvas imageCanvas: ImageCanvas) -> NSPasteboardWriting {
        let provider = NSFilePromiseProvider(fileType: kUTTypeJPEG as String, delegate: delegate)
        provider.userInfo = imageCanvas.snapshotItem
        return provider
    }
}
  • Related