简体   繁体   English

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

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

I have a tableview cell containing a custom view among other views and autolayout is used.我有一个表格视图单元格,其中包含其他视图中的自定义视图,并使用了自动布局。

The purpose of my custom view is to layout its subviews in rows and breaks into a new row if the current subview does not fit in the current line.我的自定义视图的目的是在行中布局其子视图,如果当前子视图不适合当前行,则拆分为新行。 It kind of works like a multiline label but with views.它有点像多线 label 但有视图。 I achieved this through exact positioning instead of autolayout.我通过精确定位而不是自动布局来实现这一点。

Since I only know the width of my view in layoutSubviews() , I need to calculate the exact positions and number of lines there.由于我只知道layoutSubviews()中视图的宽度,因此我需要计算那里的确切位置和行数。 This worked out well, but the frame(zero) of my view didn't match because of missing intrinsicContentSize .效果很好,但是由于缺少intrinsicContentSize ,我的视图的框架(零)不匹配。

So I added a check to the end of my calculation if my height changed since the last layout pass.因此,如果自上次布局通过后我的高度发生了变化,我在计算的末尾添加了一个检查。 If it did I update the height property which is used in my intrinsicContentSize property and call invalidateIntrinsicContentSize() .如果是这样,我会更新在我的intrinsicContentSize属性中使用的 height 属性并调用invalidateIntrinsicContentSize()

I observed that initially layoutSubviews() is called twice.我观察到最初layoutSubviews()被调用了两次。 The first pass works well and the intrinsicContentSize is taken into account even though the width of the cell is smaller than it should be.第一遍效果很好,即使单元格的宽度小于应有的宽度,也会考虑到intrinsicContentSize The second pass uses the actual width and also updates the intrinsicContentSize .第二遍使用实际宽度并更新intrinsicContentSize However the parent(contentView in tableview cell) ignores this new intrinsicContentSize .但是父级(tableview 单元格中的 contentView)忽略了这个新的intrinsicContentSize

So basically the result is that the subviews are layout and drawn correctly but the frame of the custom view is not updated/used in parent.所以基本上结果是子视图的布局和绘制正确,但自定义视图的框架没有在父级中更新/使用。

The question: Is there a way to notify the parent about the change of the intrinsic size or a designated place to update the size calculated in layoutSubviews() so the new size is used in the parent?问题:有没有办法通知父级有关固有大小的变化或指定的地方来更新layoutSubviews()中计算的大小,以便在父级中使用新的大小?

Edit:编辑:

Here is the code in my custom view.这是我的自定义视图中的代码。

FYI: 8 is just the vertical and horizontal space between two subviews仅供参考: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()
        }
    }
}

After a lot of trying and research I finally solved the bug.经过大量的尝试和研究,我终于解决了这个错误。

As @DonMag mentioned in the comments the new size of the cell wasn't recognized until a new layout pass.正如@DonMag 在评论中提到的那样,直到新的布局通过后才能识别单元格的新大小。 This could be verified by scrolling the cell off-screen and back in which showed the correct layout.这可以通过在屏幕外滚动显示正确布局的单元格并返回来验证。 Unfortunately it is harder than expected to trigger new pass as .beginUpdates() + .endUpdates() didn't do the job.不幸的是,触发新通道比预期的要难,因为.beginUpdates() + .endUpdates()没有完成这项工作。

Anyway I didn't find a way to trigger it but I followed the instructions described in this answer .无论如何,我没有找到触发它的方法,但我按照此答案中描述的说明进行操作。 Especially the part with the prototype cell for the height calculation provided a value which can be returned in tableview(heightForRowAt:) .尤其是带有用于高度计算的原型单元格的部分提供了一个可以在tableview(heightForRowAt:)中返回的值。

Swift 5: Swift 5:

This is the code used for calculation:这是用于计算的代码:

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

The value is only calculated once and the cached as the size doesn't change anymore in my case.该值仅计算一次,并且在我的情况下缓存的大小不再改变。

Then the value can be returned in the delegate:然后可以在委托中返回该值:

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

I only used for the first cell as it is my header cell and there is only one section.我只用于第一个单元,因为它是我的 header 单元,并且只有一个部分。

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

相关问题 如何在 Swift 中设置自定义视图的内在内容大小? - How to set a custom view's intrinsic content size in Swift? iOS表格视图单元格动态尺寸-LayoutSubviews - iOS Table View Cell Dynamic Size - LayoutSubviews iOS:如果我将UILabel添加到UIView,则视图是否具有标签的固有内容大小? - iOS: if I add UILabel to a UIView, will the view has intrinsic content size of the label? 应用程序在自定义视图iOS 7.0中的覆盖layoutSubviews方法上崩溃 - Application getting crash on overriding layoutSubviews method in custom view iOS 7.0 UITableViewCell在使用自定义layoutSubviews()出现在屏幕上后隐藏部分内容 - UITableViewCell hides part of content after appearing on the screen with custom layoutSubviews() iOS 13 测试版中 WKWebView 的内在内容大小问题 - WKWebView's Intrinsic Content Size issue in iOS 13 beta 如何正确地在具有固有内容大小的视图之间进行约束? - How correctly make constraints between view with intrinsic content size? 是否在更改视图大小时调用layoutSubviews? - Is layoutSubviews called whenever view's size is changed? iOS自定义UIView设计:init与layoutSubviews - iOS custom UIView design: init vs layoutSubviews drawRect是否会更改内部内容的大小? - Does drawRect change intrinsic content size?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM