如何像 UILabel 一样创建具有 intrinsicContentSize 的视图?

Posted

技术标签:

【中文标题】如何像 UILabel 一样创建具有 intrinsicContentSize 的视图?【英文标题】:How can I create a view has intrinsicContentSize just like UILabel? 【发布时间】:2017-02-20 10:42:43 【问题描述】:

我想创建一个自定义视图,可以根据内容自行调整大小。

例如,我为一个UILabel设置了水平约束,然后它会自动换行。

我想知道系统如何在布局之前知道标签的宽度?

我打印 updateConstraintslayoutSubviewsintrinsicContentSize 方法来尝试了解一切进展情况。

首先,我设置 numberOfLine = 1,所以日志是这样的:

TagLabel will updateConstraints
TagLabel intrinsicContentSize 447.5 20.5
TagLabel did updateConstraints
TableViewCell will updateConstraints
TableViewCell did updateConstraints
TableViewCell will layoutSubviews
TableViewCell did layoutSubviews
TagLabel will layoutSubviews
TagLabel did layoutSubviews
TagLabel will layoutSubviews
TagLabel did layoutSubviews

然后我设置 numberOfLine = 0,日志变化如下:

TagLabel will updateConstraints
TagLabel intrinsicContentSize 428.0 20.5
TagLabel did updateConstraints
TableViewCell will updateConstraints
TableViewCell did updateConstraints

// extra calls
TagLabel will updateConstraints
TagLabel intrinsicContentSize 428.0 20.5
TagLabel did updateConstraints
TagLabel intrinsicContentSize 428.0 20.5
TagLabel intrinsicContentSize 371.0 41.0

TableViewCell will layoutSubviews
TableViewCell did layoutSubviews

// extra calls
TagLabel will updateConstraints
TagLabel intrinsicContentSize 428.0 20.5
TagLabel did updateConstraints
TagLabel will updateConstraints
TagLabel intrinsicContentSize 428.0 20.5
TagLabel did updateConstraints
TagLabel will updateConstraints
TagLabel intrinsicContentSize 371.0 41.0
TagLabel did updateConstraints
TagLabel will updateConstraints
TagLabel intrinsicContentSize 428.0 20.5
TagLabel did updateConstraints
TagLabel will updateConstraints
TagLabel intrinsicContentSize 371.0 41.0
TagLabel did updateConstraints

TagLabel will layoutSubviews
TagLabel did layoutSubviews
TagLabel will layoutSubviews
TagLabel did layoutSubviews

我发现系统在layoutSubviews之前正确计算了标签的高度。

那么系统是如何知道标签的宽度从而计算出layoutSubviews之前的高度呢?

欣赏。

【问题讨论】:

我认为UILabel.preferredMaxLayoutWidth 与这个问题有关。不过,我不知道系统是如何知道layoutSubviews之前的宽度的。 【参考方案1】:

首先,您需要明确是否要创建新的UIView 或子类UILabel

您可以在不了解系统如何处理计算的情况下使用大量模板代码。我进一步假设您想从头开始制作自定义视图。

对于这个解释,我们将使用UILabel 作为我们新自定义视图的示例和指南。

您需要了解的是,通过设置numberOfLines 属性,您会更改处理UILabel 内部计算的方式。您可以通过单击测试项目中的 NOL 按钮进行测试。

在创建自定义视图时,您将有多种方法来计算位于自定义视图子类中的自定义视图的大小。要检查这一点,请转到 EXCustomLabelV 并查看:

override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect 

控制台中的方法输出。您将看到第一个约束更改。该值将变为 566.667,而高度仍保持为 24。结果是添加了文本截断的“...”。但是,如果您将 numberOfLines 属性设置为 0,则约束将更改为 195.333 宽度和 71.6667 高度。

这里正在发生一些重要的事情。系统将始终使用您的常量约束来计算您的自定义视图大小/位置。但是在根据“外部”约束和“内部”设置计算(自定义视图的)子视图时,您将不得不进行调整。

例如,EXCustomLabelV 的宽度始终为 200。无论。但是,内部 textRect 不会有那个大小。它有时会在 200 左右,有时会超过 200,有时会更少。这些计算受 numberOfLines 的影响。我告诉你这个是因为你的自定义视图中可能有一个图像,或者两个带有姓氏和姓氏的文本字段。

系统如何在布局前知道标签的宽度? 它计算它。它首先通过检查最后一个约束(如果有)的宽度来询问“周围”。它向子视图(您的自定义类)询问其约束,然后进行计算。约束就是规则。 AutoLayout 将在计算时检查这些规则。

系统如何知道标签的高度? 它计算它。它首先检查UILabel numberOfLines

在您的自定义视图中,您应该有自定义的计算方法。在测试项目中你可以看到:

override func draw(_ rect: CGRect)

总是最后调用。完成所有计算后,您将获得视图的最终大小。您将始终看到您的视图宽度将等于最后一个宽度约束。

随意使用我的测试项目进行您自己的计算并了解UILabel 处理视图大小计算。

创建自定义视图时,您必须自行进行这些计算并手动更新尺寸和约束。然后系统应该能够轻松地更新您的视图。我建议您尝试自己计算视图的框架大小。当你经历那种“痛苦”时,你就会明白AutoLayout为你做了多少。

Test project repo link

【讨论】:

【参考方案2】:

我认为[view layoutIfNeeded] 会解决你的问题。

layoutIfNeeded 方法是一个同步调用,它告诉系统您想要一个视图及其子视图的布局和重绘,并且您希望它立即完成,而无需等待更新周期。

【讨论】:

谢谢。它显然有效。标签在layoutIfNeed 之后有框架,我可以用它做点什么。但我很好奇为什么系统在布局之前就知道标签的宽度。有没有其他方法可以在布局前了解一些关于标签的信息? 不客气!只要看看这个 *** 问题,它就会给出你想要的确切定义! ***.com/questions/1182945/how-is-layoutifneeded-used【参考方案3】:

老问题,但其他答案并不能真正回答核心问题。

那么系统如何知道标签的宽度以便计算 layoutSubviews 之前的高度?

UILabel 的intrinsicContentSize 方法最初返回一个(宽度,高度),它对应于所有没有换行符的完全布局的行。也就是说,它已经对文本进行了虚拟布局以找出它的大小。然后它返回一个等于最长线大小的宽度。

但是,如果为宽度小于最长行的 UILabel 设置了大小 - 这样就需要换行 - UILabel 会调用其invalidateIntrinsicContentSize 方法。这向布局系统表明它需要进行第二次传递。在第二遍中,intrinsicContentSize 返回一个适合给定宽度和带有换行符的文本的大小。

正如您在日志中看到的,可能需要进行几次布局传递才能获得正确的大小和布局,尤其是当您有多个带有换行符的 UILabel 时。

【讨论】:

以上是关于如何像 UILabel 一样创建具有 intrinsicContentSize 的视图?的主要内容,如果未能解决你的问题,请参考以下文章

如何创建具有最大宽度的多行 UILabel?

如何检测 UILabel(没有设置宽度只是 AutoLayout)是不是像(你好我是测试......)一样被截断?

创建类似于选中的 UITableViewCell 的 UILabel 背景

UILabel 使用 AutoLayout 实现具有居中放置和右侧按钮的 autoheight

具有不同字体大小和颜色的UILabel文本[重复]

如何创建像 Facebook 一样的标题提醒效果?