Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TableView scrolls to middle when a new row is inserted

I have a table view that displays messages that I fetch from over the network.

When a new message comes in, it should be added to the tableview at the bottom.

When a new messages comes in, it is added to the bottom of the tableview properly, but the tableview then immediately scrolls to the middle. (So if I had say 100 cells in there now, cell 50 would be in the middle of the scroll view now).

The weird thing is that this only happens if the scroll point before the insert is in the lower half of the tableview.

So, if I'm looking at cell 10 of 99, and I add a row, it gets added to the bottom just fine.

But, if I'm looking at cell 75 of 99, and I add a row, the scroll position jumps up to the middle (~cell 50) again.

Here's my code for adding a new message to the tableview:

[self.tableView beginUpdates];
[self.messages addObject:message];

NSArray *indexPaths = @[[NSIndexPath indexPathForRow:(self.messages.count - 1) inSection:0]];

[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];

Why is the tableview scrolling automatically like this?

like image 541
Jeff Avatar asked Nov 12 '22 18:11

Jeff


1 Answers

I too have this problem. NSTableView and UITableView are probably similar... their default scrolling behavior both SUCK. TableView is not designed to be used as a spreadsheet style editor, so over the years I've added thousands of lines of code to make it work sort of like a spreadsheet. I wish Apple would just create a NSSpreadsheetView subclass of NSTableView to fix all this stuff: - cursor keys navigate cells - sensible scroll behavior - add hoc cell selection

Would also like to see a NSLogView subclass of NSTableView which behaves like Console.app, with a search field.

My solution was to add several category methods:

@implementation NSTableView (VNSTableViewCategory)

-(NSInteger)visibleRow
{
   NSRect theRect = [self visibleRect];
   NSRange theRange = [self rowsInRect:theRect];
   if ( theRange.length == 0 )
      return -1;
   else if ( NSLocationInRange( [self editedRow], theRange ) )
      return [self editedRow];        
   else if ( NSLocationInRange( [self clickedRow], theRange ) )
      return [self clickedRow];      
   else if ( NSLocationInRange( [self selectedRow], theRange ) )
      return [self selectedRow];
   else
      return theRange.location + (theRange.length/2);
}

-(NSRange)visibleRows
{
   NSRect theRect = [self visibleRect];
   NSRange theRange = [self rowsInRect:theRect];
   return theRange;
}

-(void)scrollRowToVisibleTwoThirds:(NSInteger)row
{
   NSRect theVisRect = [self visibleRect];
   NSUInteger numRows = [self numberOfRows];   
   NSRange visibleRows = [self rowsInRect:theVisRect];
   //NSUInteger lastVisibleRow = NSMaxRange(visibleRows);
   if ( NSLocationInRange( row, visibleRows ) )
   {
      if ( row - visibleRows.location < (visibleRows.length * 2 / 3) )
      {
         // may need to scroll up
         if ( visibleRows.location == 0 )
            [self scrollRowToVisible:0];
         else if ((row - visibleRows.location) > 2 )
            return;
      }
   }

   NSRect theRowRect = [self rectOfRow:row];

   NSPoint thePoint  = theRowRect.origin;
   thePoint.y -= theVisRect.size.height / 4; // scroll to 25% from top

   if (thePoint.y < 0 )
      thePoint.y = 0;

   NSRect theLastRowRect = [self rectOfRow:numRows-1];
   if ( thePoint.y + theVisRect.size.height > NSMaxY(theLastRowRect) )
      [self scrollRowToVisible:numRows-1];
   else
   {
      [self scrollPoint:thePoint]; // seems to be the 'preferred' method of doing this

      // kpk note: these other approaches cause redraw artifacts in many situations:
//      NSClipView *clipView = [[self enclosingScrollView] contentView];
//      [clipView scrollToPoint:[clipView constrainScrollPoint:thePoint]];
//      [[self enclosingScrollView] reflectScrolledClipView:clipView];
//      [self scrollRowToVisible:row];

//      [[[self enclosingScrollView] contentView] scrollToPoint:thePoint];
//      [[self enclosingScrollView] reflectScrolledClipView:[[self enclosingScrollView] contentView]];       
   }

}
@end // NSTableView (VNSTableViewCategory)

Usage Example:

  NSRange visRows = [toScenesTable visibleRows];
  visRows.length -= 2;
  if ( iAlwaysScroll == YES || !NSLocationInRange(row, visRows) )
  {
     [toScenesTable scrollRowToVisibleTwoThirds:row]; // VNSTableViewCategory
  }
like image 154
Keith Knauber Avatar answered Nov 28 '22 02:11

Keith Knauber