Skip to content

Commit

Permalink
Rewrote row movement code for improved animations
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Koehler committed Jul 1, 2017
1 parent da1d42e commit fda2307
Showing 1 changed file with 155 additions and 37 deletions.
192 changes: 155 additions & 37 deletions Table Tool/Document.m
Original file line number Diff line number Diff line change
Expand Up @@ -386,10 +386,11 @@ - (NSDragOperation)tableView:(NSTableView *)tableView

- (BOOL)tableView:(NSTableView *)tableView
acceptDrop:(id<NSDraggingInfo>)info
row:(NSInteger)rowBeforeRemoval
row:(NSInteger)destinationRow
dropOperation:(NSTableViewDropOperation)dropOperation
{
NSPasteboard *pboard = [info draggingPasteboard];

NSString *type = [pboard availableTypeFromArray:validPBoardTypes];
if ([type isEqualToString:TTRowInternalPboardType]) {
if ([info draggingSource] == self.tableView &&
Expand All @@ -398,49 +399,166 @@ - (BOOL)tableView:(NSTableView *)tableView
NSData *serializedDraggedRowIndexes = [pboard dataForType:TTRowInternalPboardType];
NSIndexSet *draggedRowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:serializedDraggedRowIndexes];

const NSUInteger draggedRowCount = [draggedRowIndexes count];

NSUInteger countOfIndicesBeforeDestinationRow = [draggedRowIndexes countOfIndexesInRange:NSMakeRange(0, rowBeforeRemoval)];
NSInteger destinationRow = rowBeforeRemoval - countOfIndicesBeforeDestinationRow;
NSArray *draggedRowData = [_data objectsAtIndexes:draggedRowIndexes];
return [self moveRowsAtIndexes:draggedRowIndexes toIndex:destinationRow];
}
}

return NO;
}

NSRange postInsertReselectionRange = NSMakeRange(destinationRow, draggedRowCount);
NSIndexSet *postInsertReselectionSet = [NSIndexSet indexSetWithIndexesInRange:postInsertReselectionRange];
- (void)addRedoStackOperationForMovingRowsAtIndexes:(NSIndexSet *)rowIndexes
toIndex:(NSInteger)row
{
[[self.undoManager prepareWithInvocationTarget:self] moveRowsAtIndexes:rowIndexes toIndex:row];
}

- (void)findLowerDropDestination:(NSInteger *)lowerDropDestination
upperDropDestination:(NSInteger *)upperDropDestination
forDraggedRowIndexes:(NSIndexSet *)draggedRowIndexes
droppedAtLocation:(NSInteger)dropLocation
{
*lowerDropDestination = dropLocation;
*upperDropDestination = dropLocation;

BOOL isRowAtDropLocationDragged = [draggedRowIndexes containsIndex:dropLocation];
BOOL isRowBeforeDropLocationDragged = [draggedRowIndexes containsIndex:dropLocation-1];
if (isRowAtDropLocationDragged || isRowBeforeDropLocationDragged)
{
NSUInteger lastIndex = (isRowAtDropLocationDragged ? dropLocation : dropLocation-1);
NSUInteger nextIndex = dropLocation;
while ((nextIndex = [draggedRowIndexes indexGreaterThanIndex:nextIndex]) != NSNotFound) {
if (nextIndex > lastIndex+1)
break;

NSUndoManager *undoManager = [self undoManager];
if (![undoManager isUndoing]) {
[undoManager setActionName:(draggedRowCount >= 2) ? @"Move Rows" : @"Move Row"];
}
[[self.undoManager prepareWithInvocationTarget:self] restoreRowsWithContent:[draggedRowData copy] atIndexes:[draggedRowIndexes copy]];
[[self.undoManager prepareWithInvocationTarget:self] deleteRowsAtIndexes:postInsertReselectionSet];
lastIndex = nextIndex;
*upperDropDestination = nextIndex+1;
}

nextIndex = dropLocation;
while ((nextIndex = [draggedRowIndexes indexLessThanIndex:nextIndex]) != NSNotFound) {
if (nextIndex < (*lowerDropDestination)-1)
break;

[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[self.tableView beginUpdates];

[_data removeObjectsAtIndexes:draggedRowIndexes];
[self.tableView removeRowsAtIndexes:draggedRowIndexes withAnimation:NSTableViewAnimationSlideUp];

NSEnumerator *reverseEnumerator = [draggedRowData reverseObjectEnumerator];
id nextRowToInsert = nil;
while (nextRowToInsert = [reverseEnumerator nextObject]) {
[_data insertObject:nextRowToInsert atIndex:destinationRow];
}
[self.tableView insertRowsAtIndexes:postInsertReselectionSet withAnimation:NSTableViewAnimationSlideDown];

[self.tableView endUpdates];
} completionHandler:^{
[_tableView selectRowIndexes:postInsertReselectionSet byExtendingSelection:NO];

[self dataGotEdited];
}];
*lowerDropDestination = nextIndex;
}
}
}



- (BOOL)moveRowsAtIndexes:(NSIndexSet *)draggedRowIndexes
toIndex:(NSInteger)dropLocation
{
// NOTE: no need to move the destination row to itself, as it lands at the same place anyway
// no need also for contiguous multi-selections at and around the destination row

NSInteger lowerDropDestination = dropLocation;
NSInteger upperDropDestination = dropLocation;
[self findLowerDropDestination:&lowerDropDestination
upperDropDestination:&upperDropDestination
forDraggedRowIndexes:draggedRowIndexes
droppedAtLocation:dropLocation];

NSRange rangeOfUnmovedIndexes = NSMakeRange(lowerDropDestination, upperDropDestination-lowerDropDestination+1);

const NSUInteger draggedRowCount = [draggedRowIndexes count];

NSUInteger countOfRowsBeforeDropLocation = [draggedRowIndexes countOfIndexesInRange:NSMakeRange(0, dropLocation)];

NSInteger destinationRow = dropLocation - countOfRowsBeforeDropLocation;

NSIndexSet *finalIndexesAfterDropping = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(destinationRow, draggedRowCount)];

NSArray *draggedRowData = [_data objectsAtIndexes:draggedRowIndexes];

NSIndexSet *lowerDraggedIndexes;
if (lowerDropDestination == 0) {
lowerDraggedIndexes = [NSIndexSet indexSet];
} else {
lowerDraggedIndexes = [draggedRowIndexes indexesInRange:NSMakeRange(0, dropLocation-1)
options:NSEnumerationConcurrent
passingTest:^BOOL(NSUInteger idx, BOOL * _Nonnull stop) { return YES; }];
}

NSIndexSet *upperDraggedIndexes = [draggedRowIndexes indexesInRange:NSMakeRange(dropLocation, [_data count])
options:NSEnumerationConcurrent
passingTest:^BOOL(NSUInteger idx, BOOL * _Nonnull stop) { return YES; }];

if ([lowerDraggedIndexes count] == 0 && [upperDraggedIndexes count] == 0) {
// NOTE: nothing to do
return NO;
}

if (![self.undoManager isUndoing]) {
[self.undoManager setActionName:(draggedRowCount >= 2) ? @"Move Rows" : @"Move Row"];
}

[[self.undoManager prepareWithInvocationTarget:self] addRedoStackOperationForMovingRowsAtIndexes:draggedRowIndexes toIndex:dropLocation];

[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[self.tableView beginUpdates];

[[self.undoManager prepareWithInvocationTarget:self] dataGotEdited];

[[self.undoManager prepareWithInvocationTarget:NSAnimationContext.self] endGrouping];
[[self.undoManager prepareWithInvocationTarget:self.tableView] endUpdates];

//
// update model
//

[_data removeObjectsAtIndexes:draggedRowIndexes];

NSEnumerator *reverseEnumerator = [draggedRowData reverseObjectEnumerator];
id nextRowToInsert = nil;
while (nextRowToInsert = [reverseEnumerator nextObject]) {
NSUInteger insertionIndex = (dropLocation - countOfRowsBeforeDropLocation);
[_data insertObject:nextRowToInsert atIndex:insertionIndex];
}

//
// update view
//

NSUInteger draggedIndex = [lowerDraggedIndexes firstIndex];
NSUInteger numberOfRowsMoved = 0;
while (draggedIndex != NSNotFound) {
if (!NSLocationInRange(draggedIndex, rangeOfUnmovedIndexes))
{
NSUInteger translatedOldIndex = (draggedIndex - numberOfRowsMoved);
[self.tableView moveRowAtIndex:translatedOldIndex toIndex:lowerDropDestination-1];
[[self.undoManager prepareWithInvocationTarget:self.tableView] moveRowAtIndex:lowerDropDestination-1 toIndex:translatedOldIndex];
}

draggedRowIndexes = nil;
numberOfRowsMoved++;
draggedIndex = [lowerDraggedIndexes indexGreaterThanIndex:draggedIndex];
}

NSUInteger destinationRow = upperDropDestination;
draggedIndex = [upperDraggedIndexes firstIndex];
while (draggedIndex != NSNotFound) {
if (!NSLocationInRange(draggedIndex, rangeOfUnmovedIndexes))
{
[self.tableView moveRowAtIndex:draggedIndex toIndex:destinationRow];
[[self.undoManager prepareWithInvocationTarget:self.tableView] moveRowAtIndex:destinationRow toIndex:draggedIndex];
}

return YES;
destinationRow++;
draggedIndex = [upperDraggedIndexes indexGreaterThanIndex:draggedIndex];
}
}

[[self.undoManager prepareWithInvocationTarget:_data] insertObjects:[draggedRowData copy] atIndexes:[draggedRowIndexes copy]];
[[self.undoManager prepareWithInvocationTarget:_data] removeObjectsAtIndexes:finalIndexesAfterDropping];

[self.tableView endUpdates];

[[self.undoManager prepareWithInvocationTarget:self.tableView] beginUpdates];
[[self.undoManager prepareWithInvocationTarget:NSAnimationContext.self] beginGrouping];
} completionHandler:^{
[self dataGotEdited];
}];

return NO;
return YES;
}

#pragma mark - updateTableView
Expand Down

0 comments on commit fda2307

Please sign in to comment.