6
votes

Is it necessary to subclass NSTableView or NSTableCellView to create a custom drag image when the tableView is the drag source?

If not, what is the magic method I am missing to do this? I cannot seem to find anything solid.

NSTableCellView subclasses have can (slightly mysteriously) override:

@property(retain, readonly) NSArray *draggingImageComponents (array of NSDraggingImageComponent instances that will get composited together (who knows in what fashion they get composited...))

NSTableView itself has

- (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)dragRows tableColumns:(NSArray *)tableColumns event:(NSEvent *)dragEvent offset:(NSPointPointer)dragImageOffset

But these are indeed subclassing options it seems.

Anybody know another technique here? (10.8+ is target )

2
In further testing starting with NSTableCellView first (path of least resistance!) it seems that although they provide an image, it first goes through the table view, and the table view does some weirdness to drag images based on its clip view... anything not visible in the clip view when dragging begins is not part of the drag image.uchuugaka

2 Answers

4
votes

It looks as if NSTableViewCell

@property(retain, readonly) NSArray *draggingImageComponents

does not get called automatically but must be explicitly referenced for view based table views. -draggingImageComponents can be overridden to supply the components used to composite the drag image.

- (void)tableView:(NSTableView *)tableView draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint forRowIndexes:(NSIndexSet *)rowIndexes
{
    // configure the drag image
    // we are only dragging one item - changes will be required to handle multiple drag items
    BPPopupButtonTableCellView *cellView = [self.columnsTableView viewAtColumn:0 row:rowIndexes.firstIndex makeIfNecessary:NO];
    if (cellView) {

        [session enumerateDraggingItemsWithOptions:NSDraggingItemEnumerationConcurrent
                                           forView:tableView
                                           classes:[NSArray arrayWithObject:[NSPasteboardItem class]]
                                     searchOptions:nil
                                        usingBlock:^(NSDraggingItem *draggingItem, NSInteger idx, BOOL *stop)
         {
             // we can set the drag image directly
             //[draggingItem setDraggingFrame:NSMakeRect(0, 0, myWidth, myHeight) contents:myFunkyDragImage];

             // the tableview will grab the entire table cell view bounds as its image by default.
             // we can override NSTableCellView -draggingImageComponents
             // which defaults to only including the image and text fields
             draggingItem.imageComponentsProvider = ^NSArray*(void) { return cellView.draggingImageComponents;};
         }];
    }
}
2
votes

In your data source you'll want to set the images e.g. from your tableView:draggingSession:willBeginAtPoint:forRowIndexes: method like so

   [session enumerateDraggingItemsWithOptions:NSDraggingItemEnumerationConcurrent
                                      forView:tableView
                                      classes:[NSArray arrayWithObject:[NSPasteboardItem class]]
                                searchOptions:nil
                                   usingBlock:^(NSDraggingItem *draggingItem, NSInteger index, BOOL *stop)
    {
       [draggingItem setDraggingFrame:NSMakeRect(0, 0, myWidth, myHeight)
                             contents:myFunkyDragImage];
    }];