9
votes

I've created an app that has a NSTableView which represents a series of files. From this, I want to be able to drag a row (i.e. a filename) from my NSTableView to Finder, and the file be created in that folder. However, the bit I can't work out is that I need to modify the contents of the original file, before it gets copied to Finder.

I've added the following line so I can drag outside of my NSTableView:

[self.tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];    

And I can get it to copy the actual file, provided I add a current files location to the pasteboard, using:

- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard {
    [pboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];        
    NSMutableArray* dragfiles = [[NSMutableArray alloc] init];
    NSString* file = [self.files objectAtIndex:row];
    NSString* filepath = [[[self.pathControl URL] path] stringByAppendingPathComponent:file];
    [dragfiles addObject:filepath];
    [pboard setPropertyList:dragfiles forType: NSFilenamesPboardType];
    [dragfiles release];
} 

But, because I want to modify the contents of the file, I don't want to put filepaths onto the pasteboard. I've tried using NSFileWrapper, but Finder doesnt seem to accept this as a valid format.

I've checked Google, and I've found several suggestions that you can create a temporary file and use that filepath. But, this feels ugly.

Is it possible to send data to Finder? Is there a way to solve this?

2

2 Answers

20
votes

You will most likely want to use promise files, or NSFilesPromisePboardType rather than NSFilenamesPboardType. (Note: the promise file methods dragPromisedFilesOfTypes:fromRect:source:slideBack:event: and namesOfPromisedFilesDroppedAtDestination: that that documentation talks about are the generic NSView methods. NSTableView defines more convenient methods that you'll use instead of the generic ones. That said, that should still provide info on how promise file drags work). NSTableView uses tableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes:, which is where you can do the processing of your files. In your tableView:writeRowsWithIndexes:toPasteboard: method, you'll declare NSFilesPromisePboardType, then set an array of filename extensions for the types of files you plan to write. The following is pseudo-code that outlines how you might approach it:

- (BOOL)tableView:(NSTableView *)aTableView
     writeRowsWithIndexes:(NSIndexSet *)rowIndexes
        toPasteboard:(NSPasteboard *)pboard {
     [pboard declareTypes:[NSArray arrayWithObjects:NSFilesPromisePboardType, nil]];

     NSMutableArray *filenameExtensions = [NSMutableArray array];
     NSArray *draggedFilenames = [self.files objectsAtIndexes:rowIndexes];
     for (NSString *filename in draggedFilenames) {
          NSString *filenameExtension = [filename pathExtension];
          if (filenameExtension.length) {
              [filenameExtensions addObject:filenameExtension];
          }
     }
     [pboard setPropertyList:filenameExtensions
                 forType:NSFilesPromisePboardType];
     return YES;
}

Then in your names-of-promisedFiles method:

- (NSArray *)tableView:(NSTableView *)aTableView
       namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
       forDraggedRowsWithIndexes:(NSIndexSet *)indexSet {

     NSArray *draggedFilenames = [self.files objectsAtIndexes:indexSet];

     for (NSString *filename in draggedFilenames) {
           // do your processing of files in here
          // if it may take a long time, you might need to do it on a background
          // thread

         NSString *fullPathToOriginal = nil;
          NSString *destPath =
         [[dropDestination path] stringByAppendingPathComponent:filename];


     }
     return draggedFilenames;
}

You should be able to calculate the original file path (not sure how you're determining it, so I left it nil in the code above) and the destination file path (using something like in the code above).

6
votes

Something to watch out for.

In NSPasteboard.h there is a comment that says:

/* Use of pboard types should be replaced with use of UTIs.  
   Pboard types will be deprecated in a future release. */

And after the NSFilesPromisePboardType definition says:

// Use (NSString *)kPasteboardTypeFileURLPromise

I just spent a few hours banging my head against a brick wall because I was passing in (NSString *)kPasteboardTypeFileURLPromise instead of NSFilesPromisePboardType in a few places.

It looks like they don't do quite the same thing. I couldn't work out why:

tableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes 

wasn't being called. When I switched back to NSFilesPromisePboardType,
suddenly it was called.