UITableView insert rows without scrolling

前端 未结 6 1377
我在风中等你
我在风中等你 2020-12-05 03:13

I have a list of data that I\'m pulling from a web service. I refresh the data and I want to insert the data in the table view above the current data, but I want to keep my

相关标签:
6条回答
  • 2020-12-05 03:42

    The best way I found to get my desired behavior is to not animate the insertion at all. The animations were causing the choppyness.

    Instead I am calling:

    [tableView reloadData];
    
    // set the content offset to the height of inserted rows 
    // (2 rows * 44 points = 88 in this example)
    [tableView setContentOffset:CGPointMake(0, 88)]; 
    

    This makes the reload appear at the same time as the content offset change.

    0 讨论(0)
  • 2020-12-05 03:43

    None of the answers here really worked for me, so I came up with my solution. The idea is that when you pull down to refresh a table view (or load it asynchronously with new data) the new cells should silently come and sit on top of the tableview without disturbing the user's current offset. So here goes a solution that works (pretty much, with a caveat)

    var indexpathsToReload: [IndexPath] = [] //this should contain the new indexPaths
    var height: CGFloat = 0.0
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
        UIView.performWithoutAnimation {
    
            self.tableview.reloadSections(NSIndexSet(index: 0) as IndexSet, with: .none) 
            self.tableview.layoutIfNeeded()
            indexpathsToReload.forEach({ (idx) in
                height += self.feed.rectForRow(at: idx).height
            })
            let afterContentOffset = self.tableview.contentOffset                             
            let newContentOffset = CGPoint(x: afterContentOffset.x, y: afterContentOffset.y + height)
            self.tableview.setContentOffset(newContentOffset, animated: false)
        }
    }
    

    CAVEAT (WARNING) This technique will not work if your tableview is not "full" i.e. it only has a couple of cells in it. In that case you would need to also increase the contentSize of the tableview along with the contentOffset. I will update this answer once I figure that one out.

    EXPLANATION: Basically, we need to set the contentOffset of the tableView to a position where it was before the reload. To do this we simply calculate the total height of all the new cells that were added using a pre populated indexPath array (can be prepared when you obtain the new data and add them to the datasource), these are the indexPaths for the new cells. We then use the total height of all these new cell using rectForRow(at: indexPath), and set that as the y position of the contentOffset of the tableView after the reload. The DispatchQueue.main.asyncAfter is not necessary but I put it there because I just need to give the tableview some time to bounce back to it's original position since I am doing it on a "pull to refresh" way. Also note that in my case the afterContentOffset.y value is always 0 so I could have hard coded 0 there instead.

    0 讨论(0)
  • 2020-12-05 03:52

    For further spectators looking for a Swift 3+ solution:

    You need to save the current offset of the UITableView, then reload and then set the offset back on the UITableView.

    func reloadTableView(_ tableView: UITableView) {
        let contentOffset = tableView.contentOffset
        tableView.reloadData()
        tableView.layoutIfNeeded()
        tableView.setContentOffset(contentOffset, animated: false)
    }
    

    Called by: reloadTableView(self.tableView)

    0 讨论(0)
  • 2020-12-05 03:59

    I am using self sizing cells and the estimated row height was pretty useless because the cells can vary significantly in size. So calculating the contentOffset wasn't working for me.

    The solution that I ended up with was quite simple and works perfectly. So first up I should mention that I have some helper methods that allow me to get the data element for an index path, and the opposite - the index path for a data element.

    -(void) loadMoreElements:(UIRefreshControl *) refreshControl {
      NSIndexPath *topIndexPath = [NSIndexPath indexPathForRow:0 inSection:0]
      id topElement = [myModel elementAtIndexPath:topIndexPath];
    
      // Somewhere here you'll need to tell your model to get more data
      [self.tableView reloadData];
    
      NSIndexPath *indexPath = [myModel indexPathForElement:topElement];
    
      [self.tableView scrollToRowAtIndexPath:indexPath 
                            atScrollPosition:UITableViewScrollPositionTop 
                                    animated:NO];
    
      [refreshControl endRefreshing];
    }
    
    0 讨论(0)
  • 2020-12-05 04:05

    If I understand your mission correctly,

    I did it in this way:

    if(self.tableView.contentOffset.y > ONE_OR_X_ROWS_HEIGHT_YOUDECIDE
    {
    
        self.delayOffset = self.tableView.contentOffset;
        self.delayOffset = CGPointMake(self.delayOffset.x, self.delayOffset.y+ insertedRowCount * ONE_ROW_HEIGHT);
    
        [self.tableView reloadData];
    
        [self.tableView setContentOffset:self.delayOffset animated:NO];    
    
    
    }else
    {
        [self.tableView insertRowsAtIndexPath:indexPathArray   WithRowAnimation:UITableViewRowAnimationTop];
    
    }
    

    With this code, If user is in the middle of the table and not the top, the uitableview will reload the new rows without animation and no scrolling. If user is on the top of the table, he will see row insert animation.

    Just pay attention in the code, I'm assuming the row's height are equal, if not , just calculate the height of all the new rows you are going to insert. Hope that helps.

    0 讨论(0)
  • 2020-12-05 04:08

    Just call setContentOffset before endUpdates, that works for me.

    [tableView setContentOffset:CGPointMake(0, iContentOffset)];
    [tableView endUpdates];
    
    0 讨论(0)
提交回复
热议问题