aaisataev
aaisataev

Reputation: 1683

UITableView is jumping when I insert new rows

I tried many ways to solve this problem, but I couldn't. My tableView jumps after it loads more data. I call the downloading method in willDisplay:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    let lastObject = objects.count - 1
    if indexPath.row == lastObject {
        page = page + 1
        getObjects(page: page)
    }
}

and insert rows here:

func getObjects(page: Int) {
    RequestManager.sharedInstance.getObjects(page: page, success: { objects in
        DispatchQueue.main.async(execute: {
            self.objects = self.objects + objects
            self.tableView.beginUpdates()
            var indexPaths = [IndexPath]()
            for i in 0...objects.count - 1 {
                indexPaths.append(IndexPath(row: i, section: 0))
            }
            self.tableView.insertRows(at: indexPaths, with: .bottom)
            self.tableView.endUpdates()
        });
    })
}

So what do I wrong? Why tableView jumps after inserting new rows?

Upvotes: 8

Views: 4404

Answers (4)

maxwell
maxwell

Reputation: 4156

I had a similar problem with tableView. Partially I decided this with beginUpdates() and endUpdates()

self.tableView.beginUpdates()
self.tableView.endUpdates()

But this didn't solve the problem. For iOS 11, the problem remained. I added an array with the heights of all the cells and used this data in the method tableView(_:heightForRowAt:)

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return cellHeights[indexPath.row] ?? 0
}

Also add this method tableView(_:estimatedHeightForRowAt:)

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return cellHeights[indexPath.row] ?? 0
}

After that, the jumps stopped.

Upvotes: 8

Mandeep Singh
Mandeep Singh

Reputation: 2855

I have just find the solution to stop jumping the table view while inserting multiple rows in the table View. Am facing this issue from last few days so finally I resolved the issue.

We have to just set the content offset of table view while inserting rows in the table view. You have to just pass your array of IndexPath rest you can use this function.

Hope so this method will help you.

 func insertRows() {
    if #available(iOS 11.0, *) {
        self.tableView.performBatchUpdates({
            self.tableView.setContentOffset(self.tableView.contentOffset, animated: false)
            self.tableView.insertRows(at: [IndexPath], with: .bottom)
        }, completion: nil)
    } else {
        // Fallback on earlier versions
        self.tableView.beginUpdates()
        self.tableView.setContentOffset(self.tableView.contentOffset, animated: false)
        self.tableView.insertRows(at: [IndexPath], with: .right)
        self.tableView.endUpdates()
    }
}

Upvotes: 11

Ahmed Khedr
Ahmed Khedr

Reputation: 1073

First, check your tableView(_:estimatedHeightForRowAt:) - this will never be accurate but the more likely the cell height ends up with this estimate the less work the table view will do.

So if there are 100 cells in your table view, 50 of them you are sure will end up with a height of 75 - that should be the estimate.

Also it's worth a while noting that there is no limit on the number of times the table view may ask its delegate of the exact cell height. So if you have a table view of 1000 rows there will a big performance issue on the layout out of the cells (delays in seconds) - implementing the estimate reduces drastically these calls.

Second thing you need to revisit the cell design, are there any views or controls whose height need to calculated by the table view? Like an image with top and bottom anchors equivalent to some other view whose height changes from cell to cell?

The more fixed heights these views/ controls have the easier it becomes for the table view to layout its cells.

I had the same issue with two table views, one of them had a variable height image embedded into a stack view where I had to implement the estimate. The other didn't had fixed size images and I didn't need to implement the estimate to make it scroll smoothly. Both table views use pagination.

Last but not least, arrays are structs. structs are value types. So maybe you don't want to store any heights in an array, see how many copies you're making? calculating the heights inside tableView(_:heightForRowAt:) is quite fast and efficient enough to work out really well.

Upvotes: 1

Vanita L.
Vanita L.

Reputation: 1325

Because your loop runs from 0 to objects count:

for i in 0...objects.count - 1 {
     indexPaths.append(IndexPath(row: i, section: 0))
}

The indexpaths generated counting for row 0 till object's count. and hence the rows are getting added at top of table (i.e. at row 0) and hence causing tableview to jump as you are there at bottom of tableview.

Try changing range as:

 let rangeStart = self.objects.count
 let rangeEnd = rangeStart + (objects.count - 1)
 for i in rangeStart...rangeEnd {
     indexPaths.append(IndexPath(row: i, section: 0))
 }

Hope it helps..!!!

Upvotes: 0

Related Questions