简体   繁体   中英

UITableView: Scrolling to the bottom with custom/variable cell heights is inaccurate on the initial scroll

Edit: https://streamable.com/rfym will show it in action. The first time I press Scroll To Bottom, it doesn't get all the way to the bottom. I then manually get all the way to the bottom and quickly scroll all the way back up. Once I press Scroll To Bottom again, it works correctly because it is using the cachedHeights.

So, I've seen many answers for this, but none of them seem to work for me and I'm pretty sure these doesn't work for many others as well:

tableView.setContentOffset(CGPointMake(0, CGFloat.max), animated: true) :

Boom, instantaneously, my tableView disappears.

tableView.setContentOffset(CGPointMake(0, self.tableView.contentSize.height - self.tableView.frame.size.height), animated: true) :

Inaccurate. It doesn't get all the way to the bottom. Sometimes, it overshoots!

tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: self.replies.count - 1, inSection: 0), atScrollPosition: .Bottom, animated: true)

Again, like the previous one, this is inaccurate.

I know why this is inaccurate. What I need is a solution for this...

Here is a bit of code to run through:

class RepliesTableViewController: UITableViewController {

    var cachedHeights = [Int: CGFloat]()

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.rowHeight = UITableViewAutomaticDimension
    }

    override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        self.cachedHeights[indexPath.row] = cell.frame.size.height
    }

    override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        if let height = cachedHeights[indexPath.row] {
            return height
        } else {
            return 113
        }
    }
}

Remember, only the initial scroll to the bottom doesn't go all the way . If I use my finger to scroll all the way to the bottom, then scroll up to the top and trigger a scroll to the bottom, it will be perfect.

The reason is simple: I will have triggered tableView.willDisplayCell on every single cell, which means all the heights are already cached . This means tableView.estimatedHeightForRowAtIndexPath works perfectly after the initial scroll.

However, if I never scroll to the bottom to cache all the heights and I try to programmatically scroll to the bottom, it won't get there. Why? Because I have the tableView.estimatedHeightForRowAtIndexPath return 113.

Some people might argue that I need a more accurate estimate, but I refuse to entertain this thought. My cells can be as small as 50 and as large as 5000. There is no accurate estimate .

How do I mitigate this?

Perhaps I need to cache all my heights at a different time? What time is that? I don't want to do any calculations of my own. The beauty of tableView.willDisplayCell is that I can cache cell.frame.size.height instead of doing my own tedious calculations.

Edit 2: I have a hilarious solution... which is extremely clunky.. and unacceptable... but technically it works. Appended for amusement.

func scrollToBottom() {
    self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: self.replies.count - 1, inSection: 0), atScrollPosition: .Bottom, animated: true)
}

override func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
    if !atBottom {
        self.tableView.setContentOffset(CGPointMake(0, tableView.contentOffset.y + 200), animated: true)
    }
}

override func scrollViewDidScroll(scrollView: UIScrollView) {
    atBottom = scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)
}
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 113.0; // set to whatever your "average" cell height is

This way your tableCell will reposition without the inaccuracy you mention

Update: ( Remove this function )

 override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if let height = cachedHeights[indexPath.row] {
        return height
    } else {
        return 113
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM