iOS - 自定义视图:在 layoutSubviews() 中更新后忽略了固有内容大小

Posted

技术标签:

【中文标题】iOS - 自定义视图:在 layoutSubviews() 中更新后忽略了固有内容大小【英文标题】:iOS - Custom view: Intrinsic content size ignored after updated in layoutSubviews() 【发布时间】:2019-11-14 13:24:25 【问题描述】:

我有一个 tableview 单元格,其中包含一个自定义视图以及其他视图,并且使用了自动布局。

我的自定义视图的目的是将其子视图按行布局,如果当前子视图不适合当前行,则拆分为新行。它有点像多行标签,但有视图。我通过精确定位而不是自动布局实现了这一点。

由于我只知道layoutSubviews() 中的视图宽度,因此我需要计算那里的确切位置和行数。效果很好,但由于缺少intrinsicContentSize,我的视图的帧(零)不匹配。

因此,如果自上次布局通过后我的高度发生变化,我在计算的末尾添加了一个检查。如果是这样,我会更新在我的 intrinsicContentSize 属性中使用的 height 属性并调用 invalidateIntrinsicContentSize()

我观察到最初layoutSubviews() 被调用了两次。第一遍效果很好,即使单元格的宽度小于应有的宽度,也会考虑intrinsicContentSize。第二遍使用实际宽度并更新intrinsicContentSize。但是父级(tableview 单元格中的 contentView)忽略了这个新的intrinsicContentSize

所以基本上结果是子视图的布局和绘制正确,但是自定义视图的框架没有在父视图中更新/使用。

问题: 有没有办法通知父级固有大小的变化或指定的地方更新layoutSubviews()中计算的大小以便在父级中使用新的大小?

编辑:

这是我的自定义视图中的代码。

仅供参考:8 只是两个子视图之间的垂直和水平空间

class WrapView : UIView 

    var height = CGFloat.zero

    override var intrinsicContentSize: CGSize 
        CGSize(width: UIView.noIntrinsicMetric, height: height)
    

    override func layoutSubviews() 
        super.layoutSubviews()

        guard frame.size.width != .zero else  return 

        // Make subviews calc their size
        subviews.forEach  $0.sizeToFit() 

        // Check if there is enough space in a row to fit at least one view
        guard subviews.map( $0.frame.size.width ).max() ?? .zero <= frame.size.width else  return 

        let width = frame.size.width
        var row = [UIView]()

        // rem is the remaining space in the current row
        var rem = width
        var y: CGFloat = .zero
        var i = 0

        while i < subviews.count 
            let view = subviews[i]
            let sizeNeeded = view.frame.size.width + (row.isEmpty ? 0 : 8)
            let last = i == subviews.count - 1
            let fit = rem >= sizeNeeded

            if fit 
                row.append(view)
                rem -= sizeNeeded
                i += 1
                guard last else  continue 
            

            let rowWidth = row.map  $0.frame.size.width + 8 .reduce(-8, +)
            var x = (width - rowWidth) * 0.5

            for vw in row 
                vw.frame.origin = CGPoint(x: x, y: y)
                x += vw.frame.width + 8
            

            y += row.map  $0.frame.size.height .max()! + 8
            rem = width
            row = []
        

        if height != y - 8 
            height = y - 8
            invalidateIntrinsicContentSize()
        
    

【问题讨论】:

嗨,马可。添加一些代码来显示您尝试过的内容将大大有助于获得答案。 查看minimal reproducible example 会有所帮助...但是,有一个建议:不要使用intrinsicContentSize,而是尝试在调整大小循环的末尾添加一个高度约束变量... hConstraint.constant = y。 (不需要invalidateIntrinsicContentSize() 哎呀......让hConstraint.constant = y - 8 @DonMag 我实际上做了对自定义视图有效的操作,但对单元格中的其他视图产生了影响,例如拉伸标签大于内容等等。很遗憾,这也不起作用。 @Marco - 我对 为什么 会发生这种情况有一个解释 - 不幸的是我没有解决方案。您的代码正在更新intrinsicContentSize 自动布局运行后...并且更改单元格的内容不会 自动触发tableView 重绘。您可能需要向控制器发送消息以在表格视图上运行 .beginUpdates() / .endUpdates() 对,但您最终可能会陷入无限循环。 【参考方案1】:

经过大量的尝试和研究,我终于解决了这个错误。

正如@DonMag 在 cmets 中提到的那样,直到新的布局通过后才会识别单元格的新大小。这可以通过在屏幕外滚动显示正确布局的单元格来验证。不幸的是,触发新通行证比预期的要难,因为.beginUpdates() + .endUpdates()didn't 做好工作。

无论如何我没有找到触发它的方法,但我按照answer 中描述的说明进行操作。尤其是带有原型单元格的高度计算部分提供了一个可以在tableview(heightForRowAt:)中返回的值。

斯威夫特 5:

这是用于计算的代码:

let fitSize = CGSize(width: view.frame.size.width, height: .zero)
/* At this point populate the cell with the exact same data as the actual cell in the tableview */
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
cell.bounds = CGRect(x: .zero, y: .zero, width: view.frame.size.width, height: cell.bounds.height)
cell.setNeedsLayout()
cell.layoutIfNeeded()
height = headerCell.contentView.systemLayoutSizeFitting(fitSize).height + 1

在我的情况下,该值只计算一次,并且缓存的大小不再改变。

那么可以在delegate中返回值:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
    indexPath.row == 0 ? height : UITableView.automaticDimension

我只用于第一个单元格,因为它是我的标题单元格,并且只有一个部分。

【讨论】:

我有一个类似的问题,并通过另一个解决方案解决。问题是“当单元格包含根据其宽度计算其高度的自定义视图时,单元格高度错误”***.com/a/65788730/3658767

以上是关于iOS - 自定义视图:在 layoutSubviews() 中更新后忽略了固有内容大小的主要内容,如果未能解决你的问题,请参考以下文章

iOS:在自定义视图中添加新约束依赖于视图的框架

iOS - 自定义视图:在 layoutSubviews() 中更新后忽略了固有内容大小

iOS 自定义 .xib 视图按钮点击检测在视图控制器 (Swift)

iOS自定义位置请求视图

iOS 子视图、SRP 和自定义事件

如何在ios中的自定义UITableViewCell中添加轮播视图