UITableView采用AutoLayout布局时如何获取准确预期高度?
我太懂你遇到的这个坑了——在layoutSubviews里直接拿tableView.contentSize.height经常会踩空,原因很简单:这个时机下tableView的cell可能还没完成AutoLayout的最终计算,contentSize还没更新到真实的高度值。下面给你几个经过验证的靠谱解法:
方法一:强制触发tableView完成全量布局
可以在获取contentSize前,先让tableView强制完成所有cell的布局计算,确保contentSize更新到位:
override func layoutSubviews() { super.layoutSubviews() if !isLayoutSubviewsDone { // 先触发当前可见cell的布局 tableView.layoutIfNeeded() // 滚动到最后一个cell,确保它也被布局(避免cell未加载导致高度计算缺失) let lastIndexPath = IndexPath(row: tableView.numberOfRows(inSection: 0)-1, section: 0) tableView.scrollToRow(at: lastIndexPath, at: .bottom, animated: false) // 再次触发布局,确保所有cell的高度都计算完成 tableView.layoutIfNeeded() expectedHeight = tableView.contentSize.height isLayoutSubviewsDone = true didLayoutSubivews?() } }
这个方法通过两次layoutIfNeeded+滚动到底部,逼tableView把所有cell的AutoLayout计算都做完,contentSize自然就准确了。
方法二:手动计算所有元素的高度总和
如果你的tableView section和row数量不多,直接手动累加每个cell、header、footer的高度是最稳妥的,完全绕开contentSize的坑:
override func layoutSubviews() { super.layoutSubviews() if !isLayoutSubviewsDone { var totalHeight: CGFloat = 0 let sectionCount = tableView.numberOfSections for section in 0..<sectionCount { // 累加当前section的header高度 totalHeight += tableView.rectForHeader(inSection: section).height // 累加当前section所有cell的高度 let rowCount = tableView.numberOfRows(inSection: section) for row in 0..<rowCount { let indexPath = IndexPath(row: row, section: section) totalHeight += tableView.rectForRow(at: indexPath).height } // 累加当前section的footer高度 totalHeight += tableView.rectForFooter(inSection: section).height } // 别忘了加上contentInset的上下边距 totalHeight += tableView.contentInset.top + tableView.contentInset.bottom expectedHeight = totalHeight isLayoutSubviewsDone = true didLayoutSubivews?() } }
这种方法的结果绝对精准,唯一的缺点是如果cell数量特别多,会有一点点性能开销,但大部分场景下完全可以忽略。
方法三:利用sizeThatFits让tableView自计算尺寸
UITableView的sizeThatFits方法会内部触发完整的布局逻辑,帮你算出适配内容的准确尺寸:
override func layoutSubviews() { super.layoutSubviews() if !isLayoutSubviewsDone { // 先固定tableView的宽度,保证cell布局时的宽度和最终展示一致 tableView.frame = CGRect( x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: self.bounds.width, height: tableView.frame.height ) // 传入一个极大的高度,让tableView根据内容计算合适的高度 let fittingSize = tableView.sizeThatFits( CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude) ) expectedHeight = fittingSize.height isLayoutSubviewsDone = true didLayoutSubivews?() } }
这个方法在AutoLayout场景下非常好用,尤其是你用了UITableViewAutomaticDimension的时候,只要cell的约束是完整的(从顶部到底部有连续的约束链),结果就不会出错。
最后提个小提醒:如果你用了自动cell高度,一定要确保每个cell的AutoLayout约束是闭合的——从cell的top到bottom必须有完整的约束链,没有缺失,否则不管用哪种方法,高度计算都会出问题。
内容的提问来源于stack exchange,提问作者Evgeniy Kleban

