【问题标题】:UITableView + AutoLayout + Intrinsic Size: Where/when to calculate size?UITableView + AutoLayout + 固有尺寸:在哪里/何时计算尺寸?
【发布时间】:2021-08-28 07:14:21
【问题描述】:

我创建了一个自定义视图MyIntrincView,它会在设置其内容时自动计算其高度。这在模拟器和 InterfaceBuilder 中都可以正常工作。

但是,当将MyIntrinsicView 放在UITableViewCell 中时,单元格高度正确计算。所有单元格都保持相同的初始高度,而不是自动将单元格高度设为视图的固有高度。

// A simple, custom view with intrinsic height. The height depends on
// the value of someProperty. When the property is changed setNeedsLayout
// is set and the height changes automatically.
@IBDesignable class MyIntrinsicView: UIView {
    @IBInspectable public var someProperty: Int = 5 {
        didSet { setNeedsLayout() }
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        calcContent()
    }
    
    func calcContent() {
        height = CGFloat(20 * someProperty)
        invalidateIntrinsicContentSize()
    }
    
    
    @IBInspectable var height: CGFloat = 50
    override var intrinsicContentSize: CGSize {
        return CGSize(width: super.intrinsicContentSize.width, height: height)
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        invalidateIntrinsicContentSize()
    }
}


// A simple cell which only contains a MyIntrinsicView subview. The view
// is attached to trailing, leading, top and bottom anchors of the cell.
// Thus the cell height should automatically match the height of the
// MyIntrinsicView
class MyIntrinsicCell: UITableViewCell {
    @IBOutlet private var myIntrinsicView: MyIntrinsicView!
    
    var someProperty: Int {
        get { return myIntrinsicView.someProperty }
        set {
            myIntrinsicView.someProperty = newValue
            
            // Cell DOES NOT rezise without manualle calling layoutSubviews()
            myIntrinsicView.layoutSubviews()
        }
    }
}

...
// Simple tableView delegate which should create cells of different heights 
// by giving each cell a different someProperty 
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "IntrinsicCell", for: indexPath) as? MyIntrinsicCell ?? MyIntrinsicCell()
    
    // Give all cell a different intrinsic height by setting someProperty to rowIndex
    cell.someProperty = indexPath.row
    return cell
}

我希望每个单元格都有不同的高度 (20 * someProperty = 20 * indexPath.row)。但是,所有单元格都具有相同的初始高度。

仅当显式调用 myIntrinsicView.layoutSubviews() 时,才会创建具有正确高度的单元格。

tableView 好像没有调用myIntrinsicView.layoutSubviews()这是为什么?

当使用 UILabelMyIntrinsicView 作为具有不同文本长度的单元格内容时,一切都按预期工作。因此,整个 tableView 设置是正确的(= 单元格大小是自动计算的),并且必须有办法在 UITableView 中正确使用内在大小。 那么,正确的做法是什么?

【问题讨论】:

    标签: ios uitableview intrinsic-content-size


    【解决方案1】:

    与您之前的问题here 一样,我认为您并没有真正理解intrinsicContentSize 做什么和不做什么。

    当设置UILabel 的文本时,是的,它的intrinsicContentSize 会发生变化,但这还不是全部。

    你也不想做你在layoutSubviews()里面尝试的事情......如果你的代码确实触发了变化,你将进入一个无限递归循环(再次,与您之前的问题一样)。

    查看对代码的修改:

    // A simple, custom view with intrinsic height. The height depends on
    // the value of someProperty. When the property is changed setNeedsLayout
    // is set and the height changes automatically.
    @IBDesignable class MyIntrinsicView: UIView {
        @IBInspectable public var someProperty: Int = 5 {
            didSet {
                calcContent()
                setNeedsLayout()
            }
        }
        
        func calcContent() {
            height = CGFloat(20 * someProperty)
            invalidateIntrinsicContentSize()
        }
        
        
        @IBInspectable var height: CGFloat = 50
        override var intrinsicContentSize: CGSize {
            return CGSize(width: super.intrinsicContentSize.width, height: height)
        }
        
        override func prepareForInterfaceBuilder() {
            super.prepareForInterfaceBuilder()
            invalidateIntrinsicContentSize()
        }
    }
    
    
    // A simple cell which only contains a MyIntrinsicView subview. The view
    // is attached to trailing, leading, top and bottom anchors of the cell.
    // Thus the cell height should automatically match the height of the
    // MyIntrinsicView
    class MyIntrinsicCell: UITableViewCell {
        @IBOutlet private var myIntrinsicView: MyIntrinsicView!
        
        var someProperty: Int {
            get { return myIntrinsicView.someProperty }
            set {
                myIntrinsicView.someProperty = newValue
            }
        }
    }
    

    两个旁注...

    1 - 您发布的代码显示您从 Apple 的文档中调用 myIntrinsicView.layoutSubviews()...:

    你不应该直接调用这个方法。如果要强制更新布局,请在下一次绘图更新之前调用 setNeedsLayout 方法。如果您想立即更新视图的布局,请调用 layoutIfNeeded 方法。

    2 - 对于看起来像你前进的方向,你可能会更好地操纵约束常数,而不是内在的内容大小。

    【讨论】:

    • 再次感谢您的出色回答!真正的内在视图具有影响内在高度的多个属性。 UILabel 中的链接高度取决于文本长度和字体大小。在设置视图并一个接一个地设置所有属性时,我想避免多次调用calcContent()。在设置所有属性后调用一次就足够了。这就是为什么我决定在layoutSubviews() 中调用calcContent() 而不是在属性didSet 中调用。因此,除了为所有属性创建一个大的 setter 之外,还有另一种方法吗?
    • @AndreiHerford - 以这种方式操纵intrinsicContentSize 真的很不寻常。通常,我们在视图的子视图上使用自动布局/约束来管理大小。我见过一些 非常 复杂的布局 - 并且我自己将一些布局放在一起 - 只有在非常特定的情况下,他们才使用这种 intrinsicContentSize 类型的方法。我真的认为您误解了布局的这一方面。也许如果你澄清并确切地表明你的目标是有意义的......
    • 假设一个简单的标签列表:一个将字符串数组作为输入并显示格式化标签列表作为输出的视图。每个标签/单词都带有背景。除了使用普通标签之外,大小/高度不仅由文本/标签和字体决定,还由项目和行间距、文本填充背景等决定。当然,这也可以使用 CollectionView 或通过添加 Lables 来解决子视图并对其使用自动布局约束。然而,特别是当嵌入到 TableView 等中时,一个专门的、普通的视图会更有效率,不是吗?
    • @AndreiHerford - “我想避免多次调用 calcContent()” ...好吧,您可以设置“needsCalc”类型的标志,或在设置所有属性后显式调用calcContent()。至于“专业的、简单的视图” 会更有效吗?不一定...事实上,它可能不太有效。就个人而言,我会采用最可靠、最直接的方式对其进行编码,然后然后寻找优化它的方法如果需要.
    • 再次感谢您的回答。你是对的,“我并不真正理解intrinsicContentSize 做什么和不做什么”。然而,问题是我没有找到真正解释它的来源......因此我花了几个小时试图理解如何正确使用intrinsicSize而没有一点进展。评论字段太短,无法解释我的所有问题并回答您的信息。我将创建一个新的、更精确的问题并删除这个...stackoverflow.com/q/67974271/777861
    猜你喜欢
    • 2010-09-19
    • 1970-01-01
    • 2013-10-10
    • 2019-02-02
    • 2021-12-14
    • 1970-01-01
    • 1970-01-01
    • 2023-04-11
    • 1970-01-01
    相关资源
    最近更新 更多