Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Opening a gap in NSTableView during drag and drop

Tags:

I've got a simple, single-column, view-based NSTableView with items in it that can be dragged to reorder them. During drag and drop, I'd like to make it so that a gap for the item-to-be-dropped opens up at the location under the mouse. GarageBand does something like this when you drag to reorder tracks (video here: http://www.screencast.com/t/OmUVHcCNSl). As far as I can tell, there's no built in support for this in NSTableView.

Has anyone else tried to add this behavior to NSTableView and found a good solution? I've thought of and tried a couple approaches without much success. My first thought was to double the height of the row under the mouse during a drag by sending -noteHeightOfRowsWithIndexesChanged: in my data source's -tableView:validateDrop:... method, then returning twice the normal height in -tableView:heightOfRow:. Unfortunately, best I can tell, NSTableView doesn't update its layout during drag and drop, so despite calling noteHeightOfRowsWithIndexesChanged:, the row height isn't actually updated.

Note that I'm using a view-based NSTableView, but my rows are not so complex that I couldn't move to a cell-based table view if doing so helped accomplish this. I'm aware of the easy, built-in ability to animate a gap for the dropped item after a drag is complete. I'm looking for a way to open a gap while the drag is in progress. Also, this is for an app to be sold in the Mac App Store, so it must not use private API.

EDIT: I've just filed an enhancement request with Apple requesting built in support for this behavior: http://openradar.appspot.com/12662624. Dupe if you'd like to see it too. Update: The enhancement I requested was implemented in OS X 10.9 Mavericks, and this behavior is now available using NSTableView API. See NSTableViewDraggingDestinationFeedbackStyleGap.

like image 387
Andrew Madsen Avatar asked Apr 13 '12 14:04

Andrew Madsen


1 Answers

I feel bizarre for doing this, but there's an extremely thorough answer in the queue here that appears to have been deleted by its author. In it, they provided the correct links to a working solution, which I feel need to be presented as an answer for someone else to take and run with, inclusive of them if they desire to do so.

From the documentation for NSTableView, the following caveats are tucked away for row animation effects:

Row Animation Effects

Optional constant that specifies that the tableview will use a fade for row or column removal. The effect can be combined with any NSTableViewAnimationOptions constant.

enum {     NSTableViewAnimationEffectFade = 0x1,     NSTableViewAnimationEffectGap = 0x2, }; 

Constants:

...

NSTableViewAnimationEffectGap

Creates a gap for newly inserted rows. This is useful for drag and drop animations that animate to a newly opened gap and should be used in the delegate method tableView:acceptDrop:row:dropOperation:.

Going through the example code from Apple, I find this:

- (void)_performInsertWithDragInfo:(id <NSDraggingInfo>)info parentNode:(NSTreeNode *)parentNode childIndex:(NSInteger)childIndex {     // NSOutlineView's root is nil     id outlineParentItem = parentNode == _rootTreeNode ? nil : parentNode;     NSMutableArray *childNodeArray = [parentNode mutableChildNodes];     NSInteger outlineColumnIndex = [[_outlineView tableColumns] indexOfObject:[_outlineView outlineTableColumn]];      // Enumerate all items dropped on us and create new model objects for them         NSArray *classes = [NSArray arrayWithObject:[SimpleNodeData class]];     __block NSInteger insertionIndex = childIndex;     [info enumerateDraggingItemsWithOptions:0 forView:_outlineView classes:classes searchOptions:nil usingBlock:^(NSDraggingItem *draggingItem, NSInteger index, BOOL *stop) {         SimpleNodeData *newNodeData = (SimpleNodeData *)draggingItem.item;         // Wrap the model object in a tree node         NSTreeNode *treeNode = [NSTreeNode treeNodeWithRepresentedObject:newNodeData];         // Add it to the model         [childNodeArray insertObject:treeNode atIndex:insertionIndex];         [_outlineView insertItemsAtIndexes:[NSIndexSet indexSetWithIndex:insertionIndex] inParent:outlineParentItem withAnimation:NSTableViewAnimationEffectGap];         // Update the final frame of the dragging item         NSInteger row = [_outlineView rowForItem:treeNode];         draggingItem.draggingFrame = [_outlineView frameOfCellAtColumn:outlineColumnIndex row:row];          // Insert all children one after another         insertionIndex++;     }];  } 

I'm unsure if it's really this simple, but it's at least worth inspection and outright refutal if it doesn't meet your needs.

Edit: see this answer's comments for the steps followed to the right solution. The OP has posted a more complete answer, which should be referred to by anyone looking for solutions to the same problem.

like image 165
MrGomez Avatar answered Sep 16 '22 14:09

MrGomez