为了帮助解决这个问题,我建立了一个GitHub存储库:
https://github.com/mattneub/SelfSizingCells/tree/master
目标是在表格视图中获得自定义大小的单元格,该表格基于绘制自己的文本而不是UILabel的自定义视图。我可以做到,但是它涉及到一个奇怪的布局错误,我不明白为什么需要它。时间安排似乎有些问题,但后来我不明白为什么UILabel不会出现相同的问题。
为了演示,我将示例分为三个场景。
在第一个场景中,每个单元格都包含一个UILabel,固定在内容视图的所有四个侧面上。我们要求自我调整大小的单元格,然后得到它们。看起来很棒。
在第二个场景中,UILabel已被名为StringDrawer的自定义视图替换,该视图绘制了自己的文本。就像标签一样,它固定在内容视图的所有四个侧面。我们要求自定义大小的单元格,但是如何获得它们?
为解决此问题,我根据显示的字符串给StringDrawer一个intrinsicContentSize
。基本上,我们测量字符串并返回结果大小。特别是,高度将是此视图所需的最小高度,以便以该视图的当前宽度完整显示字符串,并且单元格的大小应为此设置。
class StringDrawer: UIView {
@NSCopying var attributedText = NSAttributedString() {
didSet {
self.setNeedsDisplay()
self.invalidateIntrinsicContentSize()
}
}
override func draw(_ rect: CGRect) {
self.attributedText.draw(with: rect, options: [.truncatesLastVisibleLine, .usesLineFragmentOrigin], context: nil)
}
override var intrinsicContentSize: CGSize {
let measuredSize = self.attributedText.boundingRect(
with: CGSize(width:self.bounds.width, height:10000),
options: [.truncatesLastVisibleLine, .usesLineFragmentOrigin],
context: nil).size
return CGSize(width: UIView.noIntrinsicMetric, height: measuredSize.height.rounded(.up) + 5)
}
}
但是出了点问题。在此场景中,一些初始单元格的底部有一些额外的空白。此外,如果将这些单元格从视图中滚动出来然后又重新回到视图中,则它们看起来是正确的。其他所有单元格看起来都很好。那证明我在做的事是正确的,那么为什么它不能用于初始单元格?
好吧,我已经做了一些繁重的日志记录,并且发现在最初为可见单元格调用intrinsicContentSize
时,StringDrawer尚未正确知道其自身的最终宽度,即自动布局后将具有。我们被称为太早了。我们使用的宽度太窄,因此返回的高度太高。
在第三个场景中,我为在第二个场景中发现的问题添加了一种解决方法。效果很好!但这太可怕了。基本上,在视图控制器中,我等到视图层次结构组装好后,再通过调用beginUpdates
和endUpdates
强制表视图进行另一轮布局。
var didInitialLayout = false
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if !didInitialLayout {
didInitialLayout = true
UIView.performWithoutAnimation {
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
}
}
好的,这是我的问题:
(1)是否有更好,更省心的解决方法?
(2)为什么我们完全需要这种解决方法?特别是为什么我们的StringDrawer遇到这个问题,而UILabel却不是?显然,UIlabel 确实足够早地知道其自身的宽度,以便在布局系统对其进行查询时,可以在第一遍正确给出自己的内容大小。为什么我的StringDrawer与此不同?为什么需要额外的布局通行证?