首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >iOS -自定义视图:在layoutSubviews()中更新后忽略的内部内容大小

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

Stack Overflow用户
提问于 2019-11-14 13:24:25
回答 1查看 2.8K关注 0票数 3

我有一个tableview单元格,其中包含一个自定义视图,并且使用了autolayout。

自定义视图的目的是将其子视图布局为行,如果当前子视图不适合当前行,则将其分解为新行。它的工作方式类似于一个多行标签,但带有视图。我是通过精确的定位来实现这一点的,而不是自动收费的。

因为我只知道我在layoutSubviews()中视图的宽度,所以我需要计算那里的确切位置和行数。这很好,但是由于缺少intrinsicContentSize,我视图的框架(0)不匹配。

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

我注意到最初layoutSubviews()被调用了两次。虽然单元格的宽度比应该的要小,但第一遍工作得很好,并且考虑到了intrinsicContentSize。第二次传递使用实际宽度并更新intrinsicContentSize。然而,父(表视图单元格中的contentView)忽略了这个新的intrinsicContentSize

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

问题:是否有一种方法可以将内部大小的变化通知父级,或者指定一个位置来更新在layoutSubviews()中计算出来的大小,以便在父级中使用新的大小?

编辑:

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

FYI: 8只是两个子视图之间的垂直和水平空间。

代码语言:javascript
复制
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()
        }
    }
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-11-19 17:37:43

经过大量的努力和研究,我终于解决了这个问题。

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

无论如何,我没有找到触发它的方法,但我遵循了这个answer中描述的说明。特别是用原型单元进行高程计算的部分提供了一个可以在tableview(heightForRowAt:)中返回的数值。

Swift 5:

这是用于计算的代码:

代码语言:javascript
复制
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

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

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

代码语言:javascript
复制
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    indexPath.row == 0 ? height : UITableView.automaticDimension
}

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58857850

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档