reloadData() of UITableView with Dynamic cell heig

2020-01-23 04:04发布

I feel like this might be a common issue and was wondering if there was any common solution to it.

Basically, my UITableView has dynamic cell heights for every cell. If I am not at the top of the UITableView and I tableView.reloadData(), scrolling up becomes jumpy.

I believe this is due to the fact that because I reloaded data, as I'm scrolling up, the UITableView is recalculating the height for each cell coming into visibility. How do I mitigate that, or how do I only reloadData from a certain IndexPath to the end of the UITableView?

Further, when I do manage to scroll all the way to the top, I can scroll back down and then up, no problem with no jumping. This is most likely because the UITableViewCell heights were already calculated.

19条回答
小情绪 Triste *
2楼-- · 2020-01-23 04:05

@Igor answer is working fine in this case, Swift-4 code of it.

// declaration & initialization  
var cellHeightsDictionary: [IndexPath: CGFloat] = [:]  

in following methods of UITableViewDelegate

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  // print("Cell height: \(cell.frame.size.height)")
  self.cellHeightsDictionary[indexPath] = cell.frame.size.height
}

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
  if let height =  self.cellHeightsDictionary[indexPath] {
    return height
  }
  return UITableView.automaticDimension
}
查看更多
Rolldiameter
3楼-- · 2020-01-23 04:05

I have tried all the workarounds above, but nothing worked.

After spending hours and going through all the possible frustrations, figured out a way to fix this. This solution is a life savior! Worked like a charm!

Swift 4

let lastContentOffset = tableView.contentOffset
tableView.beginUpdates()
tableView.endUpdates()
tableView.layer.removeAllAnimations()
tableView.setContentOffset(lastContentOffset, animated: false)

I added it as an extension, to make the code look cleaner and avoid writing all these lines every time I want to reload.

extension UITableView {

    func reloadWithoutAnimation() {
        let lastScrollOffset = contentOffset
        beginUpdates()
        endUpdates()
        layer.removeAllAnimations()
        setContentOffset(lastScrollOffset, animated: false)
    }
}

finally ..

tableView.reloadWithoutAnimation()

OR you could actually add these line in your UITableViewCell awakeFromNib() method

layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale

and do normal reloadData()

查看更多
4楼-- · 2020-01-23 04:05

Overriding the estimatedHeightForRowAtIndexPath method with an high value, for example 300f

This should fix the problem :)

查看更多
爷的心禁止访问
5楼-- · 2020-01-23 04:05

Try to call cell.layoutSubviews() before returning cell in func cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell?. It's known bug in iOS8.

查看更多
Anthone
6楼-- · 2020-01-23 04:06

I ran into this today and observed:

  1. It's iOS 8 only, indeed.
  2. Overridding cellForRowAtIndexPath doesn't help.

The fix was actually pretty simple:

Override estimatedHeightForRowAtIndexPath and make sure it returns the correct values.

With this, all weird jittering and jumping around in my UITableViews has stopped.

NOTE: I actually know the size of my cells. There are only two possible values. If your cells are truly variable-sized, then you might want to cache the cell.bounds.size.height from tableView:willDisplayCell:forRowAtIndexPath:

查看更多
Explosion°爆炸
7楼-- · 2020-01-23 04:06

I use more ways how to fix it:

For view controller:

var cellHeights: [IndexPath : CGFloat] = [:]


func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    cellHeights[indexPath] = cell.frame.size.height
}

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

as the extension for UITableView

extension UITableView {
  func reloadSectionWithouAnimation(section: Int) {
      UIView.performWithoutAnimation {
          let offset = self.contentOffset
          self.reloadSections(IndexSet(integer: section), with: .none)
          self.contentOffset = offset
      }
  }
}

The result is

tableView.reloadSectionWithouAnimation(section: indexPath.section)
查看更多
登录 后发表回答