在使用自动布局的UIView子类中,正确使用intrinsicContentSize和sizeThatFits的方法是什么?

68

我提出这个(有点)简单的问题只是为了挑剔一下,因为有时我担心我可能会滥用许多UIView的API,特别是在自动布局方面。

为了让它变得超级简单,我将以一个例子来说明,假设我需要一个UIView子类,它有一个图像图标和一个多行标签;我想要的行为是我的视图的高度随着标签的高度而改变(以适应文本内容),同时,我正在使用Interface Builder进行布局,所以我有以下内容:

simple view image

其中包含一些约束条件,可为图像视图提供固定的宽度和高度,并为标签提供固定的宽度和位置(相对于图像视图):

simple view image, constraints shown

现在,如果我向标签中添加一些文本,我希望该视图的高度会相应地调整以适当地适应文本,或者保持与xib中具有相同的高度。 在自动布局之前,我通常会执行以下操作:

在CustoView子类文件中,我会重写sizeThatFits:如下:

- (CGSize) sizeThatFits:(CGSize)size{

    //this stands for whichever method I would have used
    //to calculate the height needed to display the text based on the font
    CGSize labelSize = [self.titleLabel intrinsicContentSize];

    //check if we're bigger than what's in ib, otherwise resize
    CGFloat newHeight = (labelSize.height <= 21) ? 51: labelSize.height+20;

    size.height = newHeight;

    return size;

}

然后我会给它起个类似这样的名字:

myView.titleLabel.text = @"a big text to display that should be more than a line";
[myView sizeToFit];

现在,考虑到约束条件,我知道自动布局系统会调用视图树元素的 intrinsicContentSize 方法来确定它们的大小并进行计算。因此,我应该重写子视图中的 intrinsicContentSize 方法,返回与之前展示的 sizeThatFits: 方法完全相同的内容,唯一不同的是,在以前调用 sizeToFit 时,我的视图被正确调整大小,但是现在使用自动布局和 xib,这种方式行不通。

当然,我可以在我的子类中每次编辑文本时调用 sizeToFit,并覆盖 intrinsicContentSize 方法,返回与 sizeThatFits: 完全相同的大小,但我认为这不是正确的方法。

我正在考虑覆盖 needsUpdateConstraintsupdateConstraints,但由于我的视图的宽度和高度是从 xib 中的自动调整掩码推断和翻译而来的,所以这还不太合理。

那么,你认为实现这里展示的内容,并完全支持自动布局的最简单和正确的方法是什么呢?

1个回答

85

我认为您不需要定义intrinsicContentSize。

有两个原因可以支持这种想法:

  1. 当Auto Layout文档讨论intrinsicContentSize时,它将其称为与“叶视图”相关的重要内容,例如按钮或标签,其中大小可以仅基于它们的内容计算。这个想法是,它们是视图层次结构树中的叶子,而不是分支,因为它们不由其他视图组成。

  2. intrinsicContentSize实际上不是Auto Layout中的“基本”概念。基本概念只是约束和由约束绑定的属性。intrinsicContentSize、内容吸收优先级和压缩阻力优先级实际上只是用于生成关于大小的内部约束的便利工具。最终大小只是这些约束与所有其他约束以通常方式交互的结果。

那么呢?因此,如果您的“自定义视图”实际上只是一些其他视图的组合,则不需要定义intrinsicContentSize。您可以定义创建所需布局的约束,这些约束也将产生所需的大小。

在您描述的特定情况下,我会从标签到superview设置一个>=0的底部空间约束,另一个从图像到superview,然后还设置了一个高度为零的低优先级约束整个视图。低优先级约束将尝试收缩组合,而其他约束则阻止其缩小到足以裁剪其子视图的程度。

如果您从未明确定义intrinsicContentSize,那么如何看到由这些约束产生的大小?一种方法是强制布局,然后观察结果。

另一种方法是使用systemLayoutSizeFittingSize:(在iOS 8中还有一个鲜为人知的systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:)。它更接近于sizeThatFits:而不是intrinsicContentSize。它是系统用于计算您的视图的适当大小的方法,考虑到它包含的所有约束,包括内在内容尺寸约束以及所有其他约束。

很不幸,如果你有一个多行标签,你很可能需要配置preferredMaxLayoutWidth才能得到良好的结果,但那是另一回事...


systemLayoutSizeFittingSize:… 方法的使用技巧很好,谢谢。UILabel 的 preferredMaxLayoutWidth 有什么特别的问题吗?它可以自动调整大小以适应其他约束条件,对吧? - Benjohn
你需要设置preferredMaxLayoutWidth,以便intrinsicContentSize返回使用换行的大小(我相信文本和preferredMaxLayoutWidth是唯一的内在内容)。我认为唯一自动设置它的方法是在某个地方重写layoutSubviews并根据完成的布局值进行设置。相比之下,systemLayoutSizeFittingSize:查看所有约束条件,而不仅仅是来自intrinsicContentSize的约束条件。因此,它将受到(1)影响宽度的其他约束条件的影响,以及(2)numberOfLines=0和其参数targetSize的组合的影响。 - algal
我也在遇到这个问题,似乎无法将高度为零的低优先级约束条件设置为整个视图。Interface Builder 不允许我在 .xib 文件的主视图上设置任何约束条件。有什么想法吗? - Bruno Morgado
有趣的是,iOS 9的新UIStackView使用intrinsicContentSize来计算其子视图的大小,而不是使用sizeThatFits等方法。这或多或少地迫使开发人员重写该方法并直接调用sizeThatFits :/ - fatuhoku
1
哦,真的吗?如果 UIStackView 直接使用 intrinsicContentSize,我会感到非常惊讶。我期望它使用 systemLayoutSizeFittingSize: 的短或长版本之一。如果是这种情况,那么只需建立适当的内部约束即可,而无需覆盖 intrinsicContentSize。我的理解是,拥有 intrinsicContentSize 的唯一效果是导致系统自动生成某些不必要的约束,以表达首选大小。 - algal
我不能同意你的观点。如果你想让一个基于框架的布局复合视图像约束基础视图一样工作,你需要像UILabel一样实现intrinsicContentSize。当混合使用自动布局和基于框架的手动布局时,你需要这样做。 - DawnSong

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接