详解intrinsicContentSize 及 约束优先级/content Hugging/content Compression Resistance

在了解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是什么东西。

Intrinsic Contenet Size – Intrinsic Content Size:固有大小。顾名思义,在AutoLayout中,它作为UIView的属性(不是语法上的属性),意思就是说我知道自己的大小,如果你没有为我指定大小,我就按照这个大小来。 比如:大家都知道在使用AutoLayout的时候,UILabel是不用指定尺寸大小的,只需指定位置即可,就是因为,只要确定了文字内容,字体等信息,它自己就能计算出大小来。

UILabel,UIImageView,UIButton等这些组件及某些包含它们的系统组件都有 Intrinsic Content Size 属性。 

也就是说,遇到这些组件,你只需要为其指定位置即可。大小就使用Intrinsic Content Size就行了。

在代码中,上述系统控件都重写了UIView 中的 -(CGSize)intrinsicContentSize: 方法。 

并且在需要改变这个值的时候调用:invalidateIntrinsicContentSize 方法,通知系统这个值改变了。

所以当我们在编写继承自UIView的自定义组件时,也想要有Intrinsic Content Size的时候,就可以通过这种方法来轻松实现。

Intrinsic冲突 – 一个UIView有了 Intrinsic Content Size 之后,才可以只指定位置,而不用指定大小。并且才可能会触发上述两个约束。 但是问题又来了,对于上述这种UIView来说,只指定位置而不指定大小,有的时候会有问题。 我们用UILabel来举例吧(所有支持Intrinsic Content Size 的组件都有此问题)。 2个UILabel,UILabel1(文字内容:UILabel1)和UILabel2(文字内容:UILabel2),其内容按照下面说明布局: - 2个UILabel距离上边栏为50点。 - UILabel1与左边栏距离为10,UILabel2左面距离UILabel1为10点。 因为都具有Intrinsic属性,所以不需要指定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 的情况就不多说了,原理相同。

在代码中修改UIView的这两个优先级

[labelsetContentHuggingPriority:UILayoutPriorityDefaultHighforAxis:UILayoutConstraintAxisHorizontal];    [labelsetContentCompressionResistancePriority: UILayoutPriorityDefaultHighforAxis:UILayoutConstraintAxisHorizontal];

1

2

Priority是个enum:

typedef float UILayoutPriority;static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) =1000; // A required constraint.  Donotexceed this.static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) =750; // Thisisthe priority levelwithwhich a button resists compressing its content.static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) =250; // Thisisthe priority level at which a button hugs its contents horizontally.static const UILayoutPriority UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) =50; //Whenyou send -[UIView systemLayoutSizeFittingSize:], the size fitting most closelytothe target size (the argument)iscomputed.  UILayoutPriorityFittingSizeLevelisthe priority levelwithwhich the view wantstoconformtothe target sizeinthat computation.  It'squite low.  Itisgenerallynotappropriatetomake a constraint at exactly this priority.  You wanttobe higherorlower.

1

2

3

4

5

Axis表示横向及纵向:

typedefNS_ENUM(NSInteger, UILayoutConstraintAxis) {    UILayoutConstraintAxisHorizontal =0,    UILayoutConstraintAxisVertical =1};

1

2

3

4

创建自定义具有 Intrinsic Content Size 功能的组件

代码及注释如下:

//IntrinsicView.h#import@interfaceIntrinsicView:UIView@property(nonatomic)CGSizeextendSize;@end

1

2

3

4

5

6

//IntrinsicView.m#import"IntrinsicView.h"staticboolcloseIntrinsic =false;//测试关闭Intrinsic的影响@implementationIntrinsicView- (instancetype)init{self= [superinit];if(self) {//不兼容旧版Autoreizingmask,只使用AutoLayout//如果为YES,在AutoLayout中则会自动将view的frame和bounds属性转换为约束。self.translatesAutoresizingMaskIntoConstraints=NO;    }returnself;}//当用户设置extendSize时,提示系统IntrinsicContentSize变化了。-(void)setExtendSize:(CGSize)extendSize{    _extendSize = extendSize;//如果不加这句话,在view显示之后(比如延时几秒),再设置extendSize不会有效果。//本例中也就是testInvalidateIntrinsic的方法不会产生预期效果。[selfinvalidateIntrinsicContentSize];}//通过覆盖intrinsicContentSize函数修改View的Intrinsic的大小-(CGSize)intrinsicContentSize{if(closeIntrinsic) {returnCGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);    }else{returnCGSizeMake(_extendSize.width, _extendSize.height);    }}@end

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

//测试代码#import"ViewController.h"#import"newViewCtlViewController.h"#import"IntrinsicView.h"@interfaceViewController()@end@implementationViewController-(void)viewDidLoad{    [superviewDidLoad];    [selftestIntrinsicView];}+-(void) testIntrinsicView{    IntrinsicView *intrinsicView1 = [[IntrinsicView alloc] init];    intrinsicView1.extendSize= CGSizeMake(100,100);    intrinsicView1.backgroundColor= [UIColorgreenColor];    [self.viewaddSubview:intrinsicView1];    [self.viewaddConstraints:@[//距离superview上方100点[NSLayoutConstraint constraintWithItem:intrinsicView1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.viewattribute:NSLayoutAttributeTop multiplier:1constant:100],//距离superview左面10点[NSLayoutConstraint constraintWithItem:intrinsicView1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.viewattribute:NSLayoutAttributeLeft multiplier:1constant:10],    ]];    IntrinsicView *intrinsicView2 = [[IntrinsicView alloc] init];    intrinsicView2.extendSize= CGSizeMake(100,30);    intrinsicView2.backgroundColor= [UIColorredColor];    [self.viewaddSubview:intrinsicView2];    [self.viewaddConstraints:@[//距离superview上方220点[NSLayoutConstraint constraintWithItem:intrinsicView2 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.viewattribute:NSLayoutAttributeTop multiplier:1constant:220],//距离superview左面10点[NSLayoutConstraint constraintWithItem:intrinsicView2 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.viewattribute:NSLayoutAttributeLeft multiplier:1constant:10],                                ]];    [selfperformSelector:@selector(testInvalidateIntrinsic:) withObject:intrinsicView2 afterDelay:2];}-(void) testInvalidateIntrinsic:(IntrinsicView *)view{    view.extendSize= CGSizeMake(100,80);}@end

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

代码效果如下:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容