3
votes

I'm writing a Mac app with a view-based table view. It's a list of images, which I intend for the user to be able to drag to the Finder to save each image to a file.

The data source owns an array of custom model objects. The model objects all conform to the NSPasteboardWriting protocol as follows:

- (NSArray *)writableTypesForPasteboard:(NSPasteboard *)pasteboard {
    //self.screenshotImageDataType is set after the image is downloaded, by examining the data with a CGImageSource.
    //I have verified that it is correct at this point. Its value in my test was @"public.jpeg" (kUTTypeJPEG).
    return @[ self.screenshotImageDataType, (__bridge NSString *)kUTTypeURL, (__bridge NSString *)kPasteboardTypeFilePromiseContent ];
}

- (id)pasteboardPropertyListForType:(NSString *)type {
    if (UTTypeEqual((__bridge CFStringRef)type, (__bridge CFStringRef)self.screenshotImageDataType)) {
        return self.screenshotImageData;
    } else if (UTTypeEqual((__bridge CFStringRef)type, kUTTypeURL)) {
        return [self.screenshotImageURL pasteboardPropertyListForType:type];
    } else if (UTTypeEqual((__bridge CFStringRef)type, kPasteboardTypeFilePromiseContent)) {
        return self.screenshotImageDataType;
    }

    id plist = [self.screenshotImage pasteboardPropertyListForType:type]
        ?: [self.screenshotImageURL pasteboardPropertyListForType:type];
    NSLog(@"plist for type %@: %@ %p", type, [plist className], plist);
    return [self.screenshotImage pasteboardPropertyListForType:type]
        ?: [self.screenshotImageURL pasteboardPropertyListForType:type];
}

The URLs that my objects own are web URLs, not local files. They're the URLs the images were downloaded from.

The table view's data-source-and-delegate-in-one implements a method relating to file promises:

- (NSArray *)tableView:(NSTableView *)tableView
namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestinationURL
forDraggedRowsWithIndexes:(NSIndexSet *)rows
{
    return [[self.screenshots objectsAtIndexes:rows] valueForKeyPath:@"screenshotImageURL.lastPathComponent"];
}

Logging the value of that expression produces a valid filename with the correct filename extension.

Finally, in windowDidLoad, I have sent the message that turns this whole mess on:

//Enable copy drags to non-local destinations (i.e., other apps).
[self.tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];

The stage is set. Here's what happens when the curtains go up:

When I drag to a window owned by the Finder, the view I'm dragging to highlights, indicating that it will accept the drag.

However, when I drop the image, no file is created.

Why doesn't the file whose contents I've promised get created?

1

1 Answers

3
votes

The documentation from the NSDraggingInfo protocol's namesOfPromisedFilesDroppedAtDestination: method gives a hint:

The source may or may not have created the files by the time this method returns.

Apparently, at least in table view context, this translates to “create the files yourself, you lazy bum”.

I amended my table view data source's tableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes: method to tell each dragged model object to write itself to a file (consisting of the destination directory URL + the filename from the model object's source URL), and implemented that functionality in the model object class. All works now.