Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to scroll to row added to NSTableView after animation has finished

After adding a new row to an NSTableView I'd like to scroll to it.

When that row has been added to the end of table the scroll only scrolls to the row that was previously the last row. I initially thought that I had to wait for the animation to finish, but that hadn't solved my issue. Here's my code:

        [NSAnimationContext beginGrouping];
        [_tableView insertRowsAtIndexes:indexSet withAnimation:NSTableViewAnimationEffectGap];
        [[NSAnimationContext currentContext] setCompletionHandler:^{

            // Scroll to the first inserted row

            NSUInteger firstIndex = [indexSet firstIndex];
            [_tableView scrollRowToVisible:firstIndex];

        }];
        [NSAnimationContext endGrouping];

How can I do this?

like image 910
tarmes Avatar asked Oct 01 '22 22:10

tarmes


2 Answers

I found a solution to his problem that I'm happy with:

[_tableView insertRowsAtIndexes:indexSet withAnimation:NSTableViewAnimationEffectGap];

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    NSUInteger firstIndex = [indexSet firstIndex];
    [_tableView scrollRowToVisible:firstIndex];
}];

I'm simply delaying the scroll request until the next run loop.

like image 59
tarmes Avatar answered Oct 13 '22 10:10

tarmes


We had problems with this, so we ended up doing the scroll as the other animations happen, to keep the row on-screen. You’d call this code inside your animation grouping where you do the tableView modifications.

The code looks like this:

- (BOOL)scrollRowToVisible:(NSInteger)row animate:(BOOL)animate;
{
    LIClipView *const clipView = (id)_sourceListOutlineView.enclosingScrollView.contentView;
    const NSRect finalFrameOfRow = [_sourceListOutlineView rectOfRow:row];
    const NSRect clipViewBounds = clipView.bounds;

    if (NSIsEmptyRect(finalFrameOfRow) || _sourceListOutlineView.numberOfRows <= 1)
        return NO;

    const NSRect finalFrameOfLastRow = [_sourceListOutlineView rectOfRow:(_sourceListOutlineView.numberOfRows - 1)];
    if (NSMaxY(finalFrameOfLastRow) <= NSHeight(clipViewBounds))
        // The source list is shrinking to fully fit in its clip view (though it might still be larger while animating); no scrolling is needed.
        return NO;

    if (NSMinY(finalFrameOfRow) < NSMinY(clipViewBounds)) {
        // Scroll top of clipView up to top of row
        [clipView scrollToPoint:(NSPoint){0, NSMinY(finalFrameOfRow)} animate:animate];
        return YES;
    }

    if (NSMaxY(finalFrameOfRow) > NSMaxY(clipViewBounds)) {
        // Scroll bottom of clipView down to bottom of source, but not such that the top goes off-screen (i.e. repeated calls won't keep scrolling if the row is higher than visibleRect)
        [clipView scrollToPoint:(NSPoint){0, MIN(NSMinY(finalFrameOfRow), NSMaxY(finalFrameOfRow) - NSHeight(clipViewBounds))} animate:animate];
        return YES;
    }

    return NO;
}
like image 42
Wil Shipley Avatar answered Oct 13 '22 12:10

Wil Shipley