繁体   English   中英

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

[英]iOS - Custom view: Intrinsic content size ignored after updated in layoutSubviews()

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

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

由于我只知道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()
        }
    }
}

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

正如@DonMag 在评论中提到的那样,直到新的布局通过后才能识别单元格的新大小。 这可以通过在屏幕外滚动显示正确布局的单元格并返回来验证。 不幸的是,触发新通道比预期的要难,因为.beginUpdates() + .endUpdates()没有完成这项工作。

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

Swift 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

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

然后可以在委托中返回该值:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM