我正在尝试在tableView中显示自定义视图,并让iOS使用parent
计算每行的高度。不幸的是,我的牢房尺寸不合适(高度不好)。
我的自定义视图定义如下(由于多种原因,我没有在此部分使用AutoLayout,我不想使用它):
UITableViewAutomaticDimension
现在,我将此自定义视图包装在class MyView : UIView {
var label: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
func setupView() {
label = UILabel()
label.numberOfLines = 0
addSubview(label)
}
func setText(text: String) {
label.text = text
setNeedsLayout()
}
override func layoutSubviews() {
super.layoutSubviews()
label.frame = bounds
invalidateIntrinsicContentSize()
}
override var intrinsicContentSize: CGSize {
guard let text = label.text else { return .zero }
let width = bounds.width
let height = text.heightWithConstrainedWidth(width: width, font: label.font)
return CGSize(width: width, height: height)
}
}
中,以便在UITableViewCell
中使用它。我在这里使用AutoLayout:
UITableView
现在,使用以下viewController,我显示一个带有大文本的单元格,以查看它的高度是否自动计算:
class MyCell : UITableViewCell {
var myView: MyView!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupCell()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupCell()
}
func setupCell() {
myView = MyView()
contentView.addSubview(myView)
myView.snp.makeConstraints { make in
make.edges.equalTo(contentView)
}
}
}
不幸的是,单元格的高度等于21.即使最后class ViewController: UIViewController {
var tableView: UITableView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableView = UITableView()
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalTo(view)
}
tableView.register(MyCell.self, forCellReuseIdentifier: "MyCell")
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 30
tableView.dataSource = self
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell") as! MyCell
cell.myView.setText(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur")
return cell
}
}
intrinsicContentSize
调用返回的高度为143,也是如此。这是结果的屏幕截图:
如果单元格被循环使用,则单元格大小正确(我在屏幕外滚动单元格):
我知道MyView
的{{1}}使用它的框架,它不应该,但是我不知道如何设计一个具有固定宽度的灵活高度的UIView子类。鉴于我在MyView' intrinsictContentSize
中调用了MyView
,我认为这会有用。
如何更改此代码以便从开始时获得良好的单元格高度?
答案 0 :(得分:4)
文档表明,覆盖intrinsicContentSize
可能不是实现您想要实现的目标的最佳方式。正如您所观察到的,来自UIView类引用:
设置此属性允许自定义视图根据其内容与布局系统进行通信。此内在大小必须独立于内容框架,因为例如,无法根据更改的高度动态地将更改的宽度传达给布局系统。
MyView的bounds
在请求intrinsicContentSize
之后才会设置,并且在自动布局上下文中无法保证在调用layoutSubviews()
之前的任何时间设置。有一种方法可以解决这个问题,并将宽度传递给自定义视图,以便在intrinsicContentSize
中使用,但是,我已将其作为此答案中的第二个选项。
选项1:通过覆盖sizeThatFits(_:)
首先,在没有自动布局的情况下,将MyView及其标签子视图配置为大小并适当调整大小:
// MyView.swift
func setupView() {
label = UILabel()
label.numberOfLines = 0
label.autoresizingMask = [.flexibleWidth] // New
addSubview(label)
}
override func layoutSubviews() {
super.layoutSubviews()
label.sizeToFit() // New
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
return label.sizeThatFits(size) // New
}
MyView现在将根据其标签子视图的大小调整自身大小,标签将根据其超级视图的宽度(因为其.flexibleWidth
自动调整蒙版)及其文本内容(因为{{ 1}}设置为0)。如果MyView有其他子视图,则需要计算并返回numberOfLines
中的总大小。
其次,我们希望UITableView能够根据上面的手动布局计算其单元格和自定义子视图的高度。当自定义单元格时,UITableView会在每个单元格上调用systemLayoutSizeFitting(_:withHorizontalFittingPriority:verticalFittingPriority)
,然后在单元格上调用sizeThatFits(_:)
。 (见WWDC 2014 Session 226。)
传递给这些方法的目标大小或拟合大小是表格视图的宽度为零的宽度。水平拟合优先级为sizeThatFits(_:)
。您可以通过覆盖自定义单元子类中的其中一个方法来控制自调整单元格过程(前者用于自动布局,后者用于手动布局)并使用传入的大小宽度来计算并返回高度细胞。
在这种情况下,单元格的大小为UILayoutPriorityRequired
:
myView
你已经完成了。
选项2:通过覆盖// MyCell.swift
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
return myView.sizeThatFits(targetSize)
}
由于MyView的大小取决于其标签的大小,因此第一步是确保标签的位置和大小正确。你已经这样做了:
intrinsicContentSize
第二步是定义MyView的内在内容大小。在这种情况下,没有固有宽度(因为该尺寸将完全由单元格或其他超视图确定),并且固有高度是标签的固有高度:
// MyView.swift
override func layoutSubviews() {
super.layoutSubviews()
label.frame = bounds
}
由于您的标签是多线标签,因此在没有最大宽度的情况下无法确定其固有高度。默认情况下,在没有最大宽度的情况下,UILabel将返回适合单个行标签的内在内容大小,这不是我们想要的。
为了计算其内在内容大小,为多行标签提供最大宽度的唯一记录方法是preferredMaxLayoutWidth
属性。 UILabel的头文件为该属性提供以下注释:
//如果非零,则在为多行标签确定-intrinsicContentSize时使用此方法
所以第三步是确保// MyView.swift
override var intrinsicContentSize: CGSize {
return CGSize(width: UIViewNoIntrinsicMetric, height: label.intrinsicContentSize.height)
}
设置为合适的宽度。由于preferredMaxLayoutWidth
属性是自动布局过程的一部分,并且仅与其相关,并且您的自定义单元子类是执行自动布局的代码的唯一部分,因此设置布局首选项的适当位置在该类中:
intrinsicContentSize
如果MyView有多个多行标签,或者您希望将MyView的子视图层次结构隐藏在其超级视图中,则可以在MyView上同样创建自己的// MyCell.swift
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
myView.label.preferredMaxLayoutWidth = targetSize.width
myView.invalidateIntrinsicContentSize()
return super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
}
属性并继续操作。
上述代码将确保MyView根据其多行标签的内容计算并返回适当的preferredMaxLayoutWidth
,并且在旋转时大小无效。
答案 1 :(得分:1)
这里有一些不同的东西会给你这种行为。
你会注意到,如果你在intrinsicContentSize中打印高度值,它会首先被调用,宽度为零,因为视图还没有大小。然后你在layoutSubviews中强制标签的框架。此时,单元格已经布局,即使您使用的是invalidateIntrinsicContentSize,也不会立即重新绘制!当您将单元格滚出视图并重新进入时,它会在该点重新绘制。
蛮力解决方案是计算layoutSubviews内标签的正确高度。或者,在设置稍后要应用的文本字符串时,计算,保存和存储它。我仍然倾向于首先尝试基于约束的解决方案,即使有多个可变高度标签,它仍然适合我。有点黑客将基于 superview 的宽度 - 它已经有宽度:
override var intrinsicContentSize: CGSize {
guard
let text = label.text,
let parent = superview
else { return .zero }
let width = parent.bounds.width
let height = text.heightWithConstrainedWidth(width: width, font: label.font)
return CGSize(width: width, height: height)
}
此外,您使用灵活的高度并将MyView的所有边缘约束到内容视图。通常,您会将顶部/前导/尾随从MyView限制到内容视图,然后将内容视图的底部限制为MyView。这样,内容视图的高度将始终弯曲到MyView的高度。
答案 2 :(得分:0)
尝试将其添加到viewDidAppear:
DispatchQueue.main.async {
self.tableView.reloadData()
}
表视图中存在一个错误,有时会阻止其单元在第一次加载时正确布局。
答案 3 :(得分:0)
您只需要使文本设置器中的固有大小无效即可,而不是在layoutSubview()中做到
func setText(text: String) {
label.text = text
invalidateIntrinsicContentSize() // invalidate before layout
setNeedsLayout()
}
同样在invalidateIntrinsicContentSize()方法中,您也可以简单地做到这一点
override var intrinsicContentSize: CGSize {
return CGSize(width: bounds.width, height: label.sizeThatFits(CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)))
}