objective-ccocoanstableviewnstableviewcell

Animating NSTableView with beginning and ending array states


I've been looking over NSTableView's moveRowAtIndex:toIndex method for animating rows in a table. It's not really helpful for sorting though from what I can tell. My interpretation of how it works is, if I want to move row 0 to row 4, then the rows in between are handled appropriately. However, if I have a table view with an array backing it, and then I sort the array, I want the table view to animate from the old state to the new state. I don't know which items were the ones that moved vs the ones that shift to accommodate the moved ones.

Example:

[A,B,C,D] --> [B,C,D,A]

I know that row 0 moved to row 3 so I would say [tableView moveRowAtIndex:0 toIndex:3]. But if I apply some custom sort operation to [A,B,C,D] to make it look like [B,C,D,A], I don't actually know that row 0 moved to row 3 rather than rows 1,2, and 3 moving to rows 0,1, and 2. I would think that I should just be able to specify all of the movements (row 0 moved to row 4, row 1 moved to row 0, etc.) but the animation doesn't look correct when I try that.

Is there a better way to do this?

Edit: I found this site, which seems to do what I want but seems like a bit much for something that should be simple (at least I think it should be simple)


Solution

  • The documentation for moveRowAtIndex:toIndex: says, "Changes happen incrementally as they are sent to the table".

    The significance of 'incrementally' can be best illustrated with the transformation from ABCDE to ECDAB.

    If you just consider the initial and final indexes, it looks like:

    E: 4->0
    C: 2->1
    D: 3->2
    A: 0->3
    B: 1->4
    

    However, when performing the changes incrementally the 'initial' indexes can jump around as you transform your array:

    E: 4->0 (array is now EABCD)
    C: 3->1 (array is now ECABD)
    D: 4->2 (array is now ECDAB)
    A: 3->3 (array unchanged)
    B: 4->4 (array unchanged)
    

    Basically, you need to tell the NSTableView, step-by-step, which rows need to be moved in order to arrive at an array identical to your sorted array.

    Here's a very simple implementation that takes an arbitrarily sorted array and 'replays' the moves required to transform the original array into the sorted array:

    // 'backing' is an NSMutableArray used by your data-source
    NSArray* sorted = [backing sortedHowYouIntend];
    
    [sorted enumerateObjectsUsingBlock:^(id obj, NSUInteger insertionPoint, BOOL *stop) {
    
      NSUInteger deletionPoint = [backing indexOfObject:obj];
    
      // Don't bother if there's no actual move taking place
      if (insertionPoint == deletionPoint) return;
    
      // 'replay' this particular move on our backing array
      [backing removeObjectAtIndex:deletionPoint];
      [backing insertObject:obj atIndex:insertionPoint];
    
      // Now we tell the tableview to move the row
      [tableView moveRowAtIndex:deletionPoint toIndex:insertionPoint];
    }];