Search code examples
swifttableviewscrollview

TableView calculates wrong estimatedHeightForRowAt


I'm making a chat like application, where the tableView displays dynamic height cells.


The cells have their views&subviews constrained in the right way

So that the AutoLayout can predict the height of the cells

(Top, Bottom, Leading, Trailing)


But still - as you can see in the video - the scroll indicator bar shows that wrong heights were calculated:

It recalculates the heights when a new row is appearing.

Video: https://youtu.be/5ydA5yV2O-Q

(On the second attempt to scroll down everything is fine)


Code:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableView.automaticDimension
}

It is a simple problem. Can someone help me out?

Update 1.0

Added github:

https://github.com/krptia/Test


Solution

  • But still - as you can see in the video - the scroll indicator bar shows that wrong heights were calculated:

    So what you want is precise content height.

    For that purpose, you cannot use static estimatedRowHeight. You should implement more correct estimation like below.

        ...
    
        var sampleCell: WorldMessageCell?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            tableView.register(UINib(nibName: "WorldMessageCell", bundle: nil), forCellReuseIdentifier: "WorldMessageCell")
    
            sampleCell = UINib(nibName: "WorldMessageCell", bundle: nil).instantiate(withOwner: WorldMessageCell.self, options: nil)[0] as? WorldMessageCell
        }
    
        ...
    
        func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
            if let cell = sampleCell {
                let text = self.textForRowAt(indexPath)
                // note: this is because of "constrain to margins", which value is actually set after estimation. Do not use them to remove below
                let margin = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 20)
                // without "constrain to margins"
                // let margin = cell.contentView.layoutMargins 
                let maxSize = CGSize(width: tableView.frame.size.width - margin.left - margin.right,
                                     height: CGFloat.greatestFiniteMagnitude)
                let attributes: [NSAttributedString.Key: Any]? = [NSAttributedString.Key.font: cell.messageLabel.font]
                let size: CGRect = (text as NSString).boundingRect(with: maxSize,
                                                                     options: [.usesLineFragmentOrigin], attributes: attributes, context: nil)
                return size.height + margin.top + margin.bottom
            }
            return 100
        }
    
    

    This is too precise (actually real row height) and maybe slow, but you can do more approximate estimation for optimization.