在了解intrinsicContentSize之前,我们需要先了解2个概念:
- AutoLayout在做什么
- 约束优先级是什么意思。
如果不了解这两个概念,看intinsic content size没有任何意义。 注:由于上面这几个概念都是针对UIView或其子类(UILabel,UIImageView等等)来说的。所以下文中都用UIView指代。
AutoLayout在做什么 – 一个UIView想要显示在屏幕中,仅须有2个需要确定的元素,一是位置,二是大小。只要2者确定,UIView就可以正确显示,至于显示的内容,则由UIView自己决定(drawRect)。
没有AutoLayout的时候,我们需要通过 initWithFrame:(CGRect)这种方式来指定UIView的位置和大小。
而使用AutoLayout的过程,就是通过约束来确定UIView的位置和大小的过程。
约束优先级 – 为什么约束需要优先级?因为有的时候2个约束可能会有冲突。 比如:有一个UIView 距离父UIView的左右距离都是0,这是2个约束,此时再给此UIView加一个宽度约束,比如指定宽度为100,那么就会产生约束冲突了。
因为,这两种约束不可能同时存在,只能满足一个,那么满足谁呢?默认情况下给UIView加的这几个约束优先级都是1000,属于最高的优先级了,表示此约束必须满足。
所以这种冲突不能被iOS所允许。此时就需要修改优先级了。把其中任意一个约束的优先级改为小于1000的值即可。
iOS可以通过比较两个”相互冲突的约束”的优先级,从而忽略低优先级的某个约束,达到正确布局的目的。
用鼠标选中宽度约束,然后在屏幕右侧的菜单中,修改优先级,如下图:
这样就没有约束冲突了。因为如果一旦两个约束冲突,系统会自动忽略优先级低的约束。
上面举的这个例子有些极端,因为上面两个约束都是确定的值,而且是绝对冲突。所以如果遇到这种情况,可能选择删掉某个约束更为合适。
而约束优先级更多的时候用于解决模糊约束(相对于上面的确定值约束来说)的冲突的问题。 比如有这样一个问题:
UIView1有四个约束:距离父UIView左和上确定,宽和高也确定。
UIView2在UIView1的下面,约束也有4个:上面距离UIView1确定,左面同UIView1对齐,同UIView1等高且等宽。 此时这两个UIView应该像这样:
这是一个很普通的应用场景,假设我希望有这样一个效果: 我希望UIView2的宽度不能超过50。当UIView1宽度小于50的时候,二者等宽;当UIView1宽度大于50的时候,UIView2不受UIView1宽度的影响。 于是我给UIView2加上一条约束:宽度<=50。这时候冲突来了: 因为UIView1的宽度是定好的,而UIView2和UIView1等宽。那么UIView2的宽度就是确定的。
很显然,分为两种情况(根据UIView1的宽度不同):
若UIView1的宽度大于50,UIView2的宽度也一定大于50,这跟新加的限制宽度<=50的约束是冲突的。
否则不冲突。
更糟糕的是,实际情况中,UIView1的宽度可能不是一个确定的值。它有可能会被页面中的其他View所影响,可能还会在运行时产生变化,并不能保证它的实际宽度一定小于50。所以,一旦产生约束冲突,可能就会对应用产生不确定的影响:可能显示错乱,也可能程序崩溃。
所以我们为了得到正确的结果,应该这样处理:
当UIView1宽度小于等于50的时候,约束不冲突,修改优先级与否都是一样结果。
当UIView1宽度大于50的时候,忽略等宽约束,也就是降低等宽约束优先级。
所以我们把等宽约束的优先级修改为999。上面两条都满足,问题解决。
说到模糊约束,content Hugging/content Compression Resistance就是2个UIView自带的模糊约束。 而这两个约束存在的条件则是UIView必须指定了 Intrinsic Content Size。 在了解这两个模糊约束之前,必须了解Intrinsic Content Size是什么东西。
UILabel,UIImageView,UIButton等这些组件及某些包含它们的系统组件都有 Intrinsic Content Size 属性。 也就是说,遇到这些组件,你只需要为其指定位置即可。大小就使用Intrinsic Content Size就行了。
在代码中,上述系统控件都重写了UIView 中的 -(CGSize)intrinsicContentSize: 方法。 并且在需要改变这个值的时候调用:invalidateIntrinsicContentSize 方法,通知系统这个值改变了。
所以当我们在编写继承自UIView的自定义组件时,也想要有Intrinsic Content Size的时候,就可以通过这种方法来轻松实现。
现在问题来了,再给UILabel2加一条约束,右侧距离右边栏为10点。
很明显,如果按照约束来布局,则没办法满足2个UIlabel都使用 Intrinsic Content Size,至少某个UILabel的宽度大于Intrinsic Content Size。这种情况,我们称之为2个组件之间的“Intrinsic冲突”。
解决“Intrinsic冲突”的方案有2种:
两个UIlabel都不使用Intrinsic Content Size。为两个UIlabel增加新的约束,来显式指定它们的大小。如:给2个UIlabel增加宽度和高度约束或等宽等高约束等等。
可以让其中一个UIlabel使用Intrinsic Content Size,另一个label则自动占用剩余的空间。这时候就需要用到 Content Hugging 和 Content Compression Resistance了!具体做法在下面介绍。
一句话总结“Intrinsic冲突”:两个或多个可以使用Intrinsic Content Size的组件,因为组件中添加的其他约束,而无法同时使用 intrinsic Content Size了。
content Hugging/content Compression Resistance – 首先,这两个概念都是UIView的属性。 假设两个组件产生了“Intrinsic冲突”: 1. Content Hugging 约束(不想变大约束)表示:如果组件的此属性优先级比另一个组件此属性优先级高的话,那么这个组件就保持不变,另一个可以在需要拉伸的时候拉伸。属性分横向和纵向2个方向。 2. Content Compression Resistance 约束(不想变小约束)表示:如果组件的此属性优先级比另一个组件此属性优先级高的话,那么这个组件就保持不变,另一个可以在需要压缩的时候压缩。属性分横向和纵向2个方向。 意思很明显。上面UIlabel这个例子中,很显然,如果某个UILabel使用Intrinsic Content Size的时候,另一个需要拉伸。 所以我们需要调整两个UILabel的 Content Hugging约束的优先级就可以啦。 在这个页面可以调整优先级(拉到最下面)。分别调整两个UILabel的 Content Hugging的优先级可以得到不同的结果:
Content Compression Resistance 的情况就不多说了,原理相同。