使用 Storyboard 时,通常情况下,可能你不会去关心 NSLayoutConstraint 的 priority 属性,使用 Storyboard 提供的默认值就能达到要求。但是最近遇到对 UILabel 的如下布局要求时,就需要涉及对 priority 的修改。

NSLayoutConstraint priority 示例
NSLayoutConstraint priority 示例

如上所示,我们期望 UILabel 显示字符串较短时,以内容的多少来决定其宽度;当其显示的字符串较长时,截断其尾部,且 UILabel 的 Trailing 距离 UIButton 的 Leading 大于等于30,UIButton的宽度由其title内容决定。在 Storyboard 将 UILabel 和 UIButton 设置好约束,不改变约束的 priority 的前提下,在 Xcode6.4 的环境下会出现约束错误提示。你可先考虑为什么会出现约束错误以及如何解决这种错误。

本文内容如下:

NSLayoutConstraint priority

了解约束优先级之前,先来了解一下约束,看看 NSLayoutConstraint Class Reference 的描述。

A constraint defines a relationship between two user interface objects that must be satisfied by the constraint-based layout system. Each constraint is a linear equation with the following format:

item1.attribute1 = multiplier × item2.attribute2 + constant

In this equation, attribute1 and attribute2 are the variables that Auto Layout can adjust when solving these constraints. The other values are defined when you create the constraint.

约束可以理解为,两个界面元素之间必须满足的基于约束布局系统的关系。每个约束都是以下方式的线性表达式:

item1.attribute1 = multiplier × item2.attribute2 + constant

其中 item1, item2 表示两个界面元素,attribute1, attribute2 是两个界面的布局属性,是自动布局系统在满足这些约束的过程中可以调整的变量,由 NSLayoutAttribute 定义,包括 left, right, top, bottom, leading, trailing, width, height, centerX, centerY 和 baseline等. 对于诸如英语这种从左至右阅读的语言,leadingtrailingleftright 完全一样,但是对于诸如 Hebrew 或者 Arabic 这种从右往左阅读的语言,leadingright 相同,trailingleft 相同,通常情况下,我们使用 leadingtrailing 较多。

constant: The physical size or offset, in points, of the constraint.

multiplier: The multiplier applied to the second attribute participating in the constraint.

A valid layout is defined as a set constraints with one and only one possible solution. Valid layouts are also referred to as a nonambiguous, nonconflicting layouts. Constraints with more than one solution are ambiguous. Constraints with no valid solutions are conflicting.

一个有效的约束是指有且仅有一种方案满足所有约束。如果有多种方案或者没有一个方案满足所有约束,则这些自动布局都无效。对于 view 上的约束都是累加的,对于同一类型的约束不会覆盖,例如,如果你已对 view 添加了一个宽度约束,再次对该 view 添加宽度约束并不会将之前的宽度约束移除或者将之前的宽度约束值修改为新的约束值,你必须将之前添加的宽度约束值手动移除。对于在Storyboard中添加的约束,可以像 UIView 一样在 Class 中声明 IBOutlet 属性进行连接,从而对约束值进行修改。

Additionally, constraints are not limited to equality relationships. They can also use greater than or equal to (>=) or less than or equal to (<=) to describe the relationship between the two attributes. Constraints also have priorities between 1 and 1,000. Constraints with a priority of 1,000 are required. All priorities less than 1,000 are optional. By default, all constraints are required (priority = 1,000).

After solving for the required constraints, Auto Layout tries to solve all the optional constraints in priority order from highest to lowest. If it cannot solve for an optional constraint, it tries to come as close as possible to the desired result, and then moves on to the next constraint.

另外,约束不仅限于等量关系,它还可以是(>=)或者(<=)来描述两个属性之间的关系,由 NSLayoutRelation 定义。约束还有 1 ~ 1000 的优先级,优先级为1000的约束为必须满足,优先级为 1 ~ 999 的约束为可选约束,数字越大其优先级越高,其满足的可能性越高,自动布局系统在满足了所有优先级为 1000 的约束后,会按照优先级从高到低的顺序满足可选约束。默认情况下,所有约束优先级都是 1000,即必须满足。

Intrinsic Content Size

要理解内容压缩阻力和内容吸附性这两个概念,首先要理解内部内容尺寸Intrinsic Content Size这一概念。

每个视图都有内容压缩阻力优先级Content Compression Resistance Priority和内容吸附性优先级Content Hugging Priority。但只有当视图定义了内部内容尺寸后,这两种优先级才会起作用;否则如果都没有定义内容尺寸大小,又如何知道应该抗压缩或者吸附至什么大小呢。

那么,内部内容尺寸是指的什么,有什么作用呢?

引用自苹果官方Auto Layout指南里面对内部内容尺寸的描述:

Leaf-level views such as buttons typically know more about what size they should be than does the code that is positioning them. This is communicated through the intrinsic content size, which tells the layout system that a view contains some content that it doesn’t natively understand, and indicates how large that content is, intrinsically.

For elements such as text labels, you should typically set the element to be its intrinsic size (select Editor > Size To Fit Content). This means that the element will grow and shrink appropriately with different content for different languages.

什么意思呢?

简单来说就是,像按钮、文本标签这类视图控件,在布局的时候,它们自己内部比外部布局代码更清楚自己需要多大的尺寸来显示自己的内容。而这个尺寸就是由内部内容尺寸intrinsic content size来传达的。这就相当于,内部内容尺寸告诉布局系统:这个视图里面包含了一些你不能理解的内容,但是我给你指出了那些内容有多大。

由此可见,内部内容尺寸是为了实现视图自适应大小而准备的。

Content Compression Resistance 与 Content Hugging

关于这两个概念,最容易理解的文档说明在UIView Class Reference文档里面:

- contentCompressionResistancePriorityForAxis:

Returns the priority with which a view resists being made smaller than its intrinsic size.

The constraint-based layout system uses these priorities when determining the best layout for views that are encountering constraints that would require them to be smaller than their intrinsic size.

Subclasses should not override this method. Instead, custom views should set default values for their content on creation, typically to NSLayoutPriorityDefaultLow or NSLayoutPriorityDefaultHigh.

ContentCompressionResistancePriority: 内容压缩阻力优先级, 是指 view 阻止其视图大小被压缩到小于其内部内容尺寸的优先级, 即视图反压缩的优先级,优先级越大,视图就越不容易被压小。当自动布局系统为所有视图布局时,遇到约束要求该 view 的尺寸需要小于其内部内容尺寸会用到。

- contentHuggingPriorityForAxis:

Returns the priority with which a view resists being made larger than its intrinsic size.

The constraint-based layout system uses these priorities when determining the best layout for views that are encountering constraints that would require them to be larger than their intrinsic size.

ContentHuggingPriority:内容吸附性优先级, 是指 view 阻止其视图大小被拉伸到大于其内部内容尺寸的优先级,即视图反拉伸的优先级,优先级越大,视图就越不容易被拉大。

回到本文开头的约束冲突问题,在同一水平线上添加了两个定义了内部内容尺寸的视图 UILabel 和 UIButton,并为其添加了5个优先级为 UILayoutPriorityRequired 的约束,且此时 UILabel 和 UIButton x 轴方向的 ContentCompressionResistancePriority 优先级都是750, 自动布局系统在解决所有约束满足的时候,发现不知道如何处理优先满足 UILabel 不压缩 还是 优先满足 UIButton 不压缩,由此约束冲突产生。需求中,我们需要 UIButton 的 title 不压缩,而 UILabel 的内容可以尾部截断,由此我们可以将 UILabel 的 ContentCompressionResistancePriority 优先级调整到低于 750,此时 UIButton 的 ContentCompressionResistancePriority 750 大于 UILbael 的该值,自动布局系统在满足所有约束的过程中优先满足 UIButton 的该约束,即不压缩 UIButton 的内部内容尺寸,由此冲突解决。

参考:Content Compression Resistance和Content Hugging,该文中有一个很好的示例说明。

如果觉得本文对你有帮助,就请用微信打赏我吧^_^