Auto Layout压缩阻力及内容吸附讲解

本文为投稿文章,作者:梁炜V

在Auto Layout的使用中,有两个很重要的布局概念:Content Compression Resistance 和 Content Hugging,从字面的翻译我们大概可以分别翻译为:压缩阻力 以及内容吸附。但是光从字面意思来理解很难知道它们如何使用以及确切的设计意图。我最开始也是很迷糊而且在使用Auto Layout的过程中也没有使用过它们,直到最近稍稍研究了一下,发现它们的作用甚是巨大,所以我为了加深记忆,把我最近学习到的关于它们的概念在此稍作整理加以记录。

> 注:以下为了表述方便,将`Content Compression Resistance`记为`压缩阻力`,将`Content Hugging`记为`内容吸附`。

Content Compression Resistance

压缩阻力属性为了记忆更加形象我们可以把它理解为离我远点,不许挤到我,它的优先级(Priority)越高,它的这种抗挤压的能力也就越强,我们可以通过代码在控件的水平或垂直方向上分别设置1(最低优先级)到1000(最高优先级)之间的优先级,默认是750,例如我们可以为一个 UILabel 控件设置一个它在水平方向上优先级为 500的压缩阻力:

1

[label setContentCompressionResistancePriority:500 forAxis:UILayoutConstraintAxisHorizontal];

用图来表示,我们可以大致表示如下,视图的压缩阻力就好似它自身往外的张力,优先级越高,视图自己维持自身显示完整性的能力就越强:

Content Hugging

内容吸附 属性为了记忆方便我们可以把它理解为 抱紧(Hug),视图的大小不会随着superView的变大而扩大,而是只维持能完全显示自己内容的大小,它的这种优先级越高,吸附的能力就越强,和 压缩阻力 一样,内容吸附 的优先级也可以通过代码来设置,只是它的默认优先级是 250

   

用图来表示,我们可以大致表示如下,视图的内容吸附就好似视图自己有向内抱紧自己的力量一样,优先级越高,它的这种能力就越强:

以上讲了 内容吸附 和 压缩阻力 的基本概念,但是这两个属性是建立在 Intrinsic Content Size 这一概念上的,我们暂且把它翻译为 固有尺寸,所有基于UIView的视图都有 intrinsicContentSize 这个属性,下面我们就介绍一下什么是 固有尺寸。

Intrinsic Content Size

每个视图都有压缩阻力优先级(Content Compression Resistance Priority)和内容吸附优先级(Content Hugging Priority),但只有视图明确了它的固有尺寸后,这两种优先级才会起作用。我们首先来看一下官方的解释:

Custom views typically have content that they display of which the layout system is unaware. Overriding this method allows a custom view to communicate to the layout system what size it would like to be based on its content. This intrinsic size must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height, for example.

If a custom view has no intrinsic size for a given dimension, it can return UIViewNoIntrinsicMetric for that dimension.

大致的意思就是我们自定义的视图在默认情况下,它的固有尺寸是返回(UIViewNoIntrinsicMetric,UIViewNoIntrinsicMetric),也就是(-1,-1),只有我们根据自定义视图本身的Content来重写该方法,我们自定义的视图才能明确的知道他在显示系统中该展示的大小。

UILabel和UIButton等这些控件,系统默认是根据他们的内容实现了固有尺寸,所以我们在使用的时候只需要确定origin或者Center它们就能正确的显示。

由此可见,固有尺寸是为了实现视图的 大小自适应 而存在的。

以下我来自定义一个视图,来测试一下 固有尺寸 是否有效,由于项目中大家都是用[Masonry](http://https://github.com/SnapKit/Masonry)来处理Auto Layout,所以一下的例子都使用 Masonry 来布局。

重写Intrinsic Content Size

我们新建一个继承自UIView的自定义视图IntrinsicView,在一个ViewController中添加一个我们自定义的视图,设置它水平居中,顶部和父视图对齐。

1

2

3

4

5

6

7

8

9

10

- (void)layoutSubIntrinsicView

{

    IntrinsicView *intrinsicView = [IntrinsicView new];

    intrinsicView.backgroundColor = [UIColor colorWithRed:.2 green:.4 blue:.6 alpha:1];

    [self.view addSubview:intrinsicView];

    [intrinsicView mas_makeConstraints:^(MASConstraintMaker *make) {

    make.centerX.mas_equalTo(self.view);

    make.top.mas_equalTo(self.mas_topLayoutGuideBottom);

}];

}

运行后发现什么也没显示,因为我们没有设置它的宽高,而它默认的固有尺寸是(-1 ,-1)。我们去重写IntrinsicView的- (CGSize)intrinsicContentSize方法:

   

运行后显示如下:

1、测试内容吸附优先级

为了测试内容吸附优先级我们在页面上添加两个IntrinsicView,分别是topView和bottomView,设置他们都水平居中,然后分别和页面的顶部和底部对齐:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

- (void)layoutSubIntrinsicView

{

    IntrinsicView *topView = [IntrinsicView new];

    topView.backgroundColor = [UIColor colorWithRed:.2 green:.4 blue:.6 alpha:1];

    [self.view addSubview:topView];

    [topView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.centerX.mas_equalTo(self.view);

        make.top.mas_equalTo(self.mas_topLayoutGuideBottom);

    }];

    

    IntrinsicView *bottomView = [IntrinsicView new];

    bottomView.backgroundColor = [UIColor colorWithRed:.2 green:.4 blue:.6 alpha:1];

    [self.view addSubview:bottomView];

    [bottomView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.centerX.mas_equalTo(self.view);

        make.bottom.mas_equalTo(self.mas_bottomLayoutGuideBottom);

    }];

}

运行后展示如下:

下面我们设置 topView 和 bottomView 之间的间距为 40,也就是 topView.bottom + 40 = bottomView.top

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

- (void)layoutSubIntrinsicView

{

    IntrinsicView *topView = [IntrinsicView new];

    topView.backgroundColor = [UIColor colorWithRed:.2 green:.4 blue:.6 alpha:1];

    [self.view addSubview:topView];

    [topView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.centerX.mas_equalTo(self.view);

        make.top.mas_equalTo(self.mas_topLayoutGuideBottom);

}];

    IntrinsicView *bottomView = [IntrinsicView new];

    bottomView.backgroundColor = [UIColor colorWithRed:.2 green:.4 blue:.6 alpha:1];

    [self.view addSubview:bottomView];

    [bottomView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.centerX.mas_equalTo(self.view);

        make.top.mas_equalTo(topView.mas_bottom).offset(40);

        make.bottom.mas_equalTo(self.mas_bottomLayoutGuideBottom);

 }];

}

运行后展示效果如下:

我们发现 topView 被拉伸了,如果我们不想 topView 被拉伸,就可以利用 内容吸附 的特性,因为我们定义了 IntrinsicView 的固有尺寸,设置 topView 的 内容吸附 优先级比 bottomView 的优先级高,我们上面介绍了 内容吸附 的默认优先级是 250,我们把 topView 的 内容吸附 优先级设置为 251,在原来 layoutSubIntrinsicView 函数的最后添加如下语句:

   

运行后如下所示,达到了我们想要的效果:

>251是我随意定的比250大的值,可以是大于250小于1000的任何值。

2、测试压缩阻力优先级

我们通常会遇到如下图所示的需求:

在某个页面上水平放置两个UILabel,leftLabel 的左边和父视图的间距固定,rightLabel 的右边和父视图的右边有一个小于等于某个间隔的约束,leftLabel 和rightLabel 之间有一个固定间距,它们的宽度根据他们显示的内容自适应,关键代码如下:

   

在他们的显示内容宽度不超过父视图宽度时,两个label的内容都能正常的完全显示,但是当它们需要显示的内容长度总和超过父视图的宽度时,就会显示如下:

一个label被压缩了, rightLabel 显示不完全,如果在这种情况下,我们想 leftLabel 被压缩,而 rightLabel 尽量完全显示,由于UILabel这类控件,系统自己已经根据它们显示的实际内容实现了 固有尺寸 的方法,我们可以利用 压缩阻力 的特性,将 rightLabel 的 压缩阻力 优先级设置得比 leftLabel 高,上面介绍了 压缩阻力 的默认优先级是 750,我们把`rightLabel`的优先级设置为 751,在上面代码的最下面添加如下代码:

1

[rightLabel setContentCompressionResistancePriority:751 forAxis:UILayoutConstraintAxisHorizontal];

运行后显示如下,达到了我们预期的效果:

3、在自动计算UITableViewCell高度中的使用

对于变高cell的处理,以前我们都是在`heightForRowAtIndexPath`方法里面,拼凑要展示的变高cell的高度,当我们改变cell中两个控件在垂直方向的布局,或者再添加一个控件时,还要去修改计算cell高度的方法来适应新的变化,非常不方便。但是有了自动布局后,利用好`压缩阻力`和`内容吸附`的优先级,可以很精确很简单的由系统来计算出变高cell的高度。

假定我们有如下需求:

我们看到,这个变高cell里面高度不定的是中间的`ContentLabel`,它会根据内容长度来折行显示,UILabel要折行显示我们需要设置它的`preferredMaxLayoutWidth`和`numberOfLines`两个属性的值。

首先假定`Model`的定义如下:

   

自定义的`UITableViewCell`的关键代码如下:

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

    [_cellImageView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.left.mas_equalTo(self.contentView).offset(10);

        make.centerY.mas_equalTo(self.contentView);

        make.top.mas_greaterThanOrEqualTo(self.contentView).offset(10);

        make.bottom.mas_lessThanOrEqualTo(self.contentView).offset(-10);

    }];

    

    [_nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {

        make.left.mas_equalTo(self.cellImageView.mas_right).offset(6);

        make.right.mas_lessThanOrEqualTo(self.contentView).offset(-10);

        make.top.mas_equalTo(self.contentView).offset(10);

    }];

    

    _contentLabel.numberOfLines = 0;

    [self.contentView addSubview:_contentLabel];

    [_contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {

        make.left.mas_equalTo(self.nameLabel);

        make.top.mas_equalTo(self.nameLabel.mas_bottom).offset(6);

    }];

    

    [_companyLabel mas_makeConstraints:^(MASConstraintMaker *make) {

        make.left.mas_equalTo(self.contentLabel);

        make.right.mas_lessThanOrEqualTo(self.contentView).offset(-10);

        make.top.mas_equalTo(self.contentLabel.mas_bottom).offset(6);

        make.bottom.mas_equalTo(self.contentView).offset(-10);

    }];

    

    [_nameLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];

    [_companyLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];

    [_contentLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];

上面的代码中设置了几个`UILabel`的`内容吸附`优先级为最高,这样它们就不会随着cell高度的变化而拉伸高度。上面设置了`contentLabel`的`numberOfLines = 0`,还需要设置`preferredMaxLayoutWidth`才能正确换行显示。由于`UITableViewCell`在显示出来之前是不知道宽度的,但是为了获取正确的宽度我们可以在`- (void)layoutSubviews`方法里面设置:

1

2

3

4

5

- (void)layoutSubviews

{

    _contentLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.contentView.frame) - 128 - 10 - 6;

    [super layoutSubviews];

}

这样我们设置好cell以及Model以后,其他的方法都和普通的使用一样,唯一不一样的就是计算cell高度的`UITableView`代理方法`heightForRowAtIndexPath`,它的实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

{

    static CodeLayoutCell *singleCell = nil;

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

    

        singleCell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];

    });

    

    CellModel *model = _dataSourceArray[indexPath.row];

    if (model.cacheHeight != 0) {

        return model.cacheHeight;

    }

    [singleCell layoutIfNeeded];

    [singleCell setNewCellModel:model];

    

    CGSize size = [singleCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    model.cacheHeight = size.height + 1;

    

    return model.cacheHeight;

}

运行的效果如下:

当图片的高度大于三个`UIlabel`加上各自上下的间隔的高度时,由于我们设置了三个Label的`内容吸附`最高的优先级,所以为了满足它们的高度,图片的内容就进行了压缩,如下:

第二个cell的图片被压缩了,如何才能保证它不被压缩呢?留给读到这里的人自己实现吧!????

暂时先写到这里吧,由于刚接触这两个属性,难免会有遗误之处,请大家多多谅解!

完整的Demo,请戳[这里]

http://www.cocoachina.com/ios/20160229/15455.html

Auto Layout压缩阻力及内容吸附讲解的更多相关文章

  1. iOS 8 Auto Layout界面自动布局系列5-自身内容尺寸约束、修改约束、布局动画

    首先感谢众多网友的支持,最近我实在是事情太多,所以没有写太多.不过看到大家的反馈和评价,我还是要坚持挤出时间给大家分享我的经验.如果你对我写的东西有任何建议.意见或者疑问,请到我的CSDN博客留言: ...

  2. iOS 开发实践之 Auto Layout

    原:http://xuexuefeng.com/autolayout/?utm_source=tuicool 本文是博主 iOS 开发实践系列中的一篇,主要讲述 iOS 中 Auto Layout(自 ...

  3. [Android开发学iOS系列] Auto Layout

    [Android开发学iOS系列] Auto Layout 内容: 介绍什么是Auto Layout. 基本使用方法 在代码中写约束的方法 Auto Layout的原理 尺寸和优先级 Auto Lay ...

  4. ios auto layout demystified (一)

    Ambiguous Layout 在开发过程中,你可以通过调用hasAmbiguousLayout 来测试你的view约束是否足够的.这个会返回boolean值.如果有一个不同的frame就会返回ye ...

  5. 从 Auto Layout 的布局算法谈性能

    这是使用 ASDK 性能调优系列的第二篇文章,前一篇文章中讲到了如何提升 iOS 应用的渲染性能,你可以点击 这里 了解这部分的内容. http://t.cn/Rc4KbUC 在上一篇文章中,我们提到 ...

  6. Auto Layout 使用心得

    此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方可以参考我的 Auto Layout 设置哦,下载到本地打开就可以了. 简介 Auto ...

  7. Swift语言Auto Layout入门教程:上篇

    原文:Beginning Auto Layout Tutorial in Swift: Part 1/2,译者:@TurtleFromMars 开始用自动布局约束的方式思考吧! 更新记录:该教程由Br ...

  8. Auto Layout: Programmatic Constraints - BNR

    继续Auto Layout - BNR篇. 打开BNRDetailViewController.m文件,重载viewDidLoad方法来创建UIImageView对象.当你想要给通过加载NIB文件创建 ...

  9. Auto Layout Guide----(三)-----Anatomy of a Constraint

    Anatomy of a Constraint 剖析约束 The layout of your view hierarchy is defined as a series of linear equa ...

随机推荐

  1. C#学习笔记_05_输入输出

    05_输入输出 输出语句 Console.WriteLine( ); 将括号内内容输出到控制台,并且换行 Console.Write( ); 将括号内内容输出到控制台,不换行 Console.Writ ...

  2. 利用Flask-SQLAlchemy提供的paginate()方法实现博客文章的分页显示

    在开发blog系统的时候,我们有一个需求,就是要显示作者已经发表的blog文章的列表,或显示作者关注者的文章列表.实现这个功能并不复杂,只需要在存储文章的数据库中过滤出指定作者的文章,然后渲染HTML ...

  3. elasticsearch 文档阅读笔记(三)

    文档 elasticsearch是通过document的形式存储数据的,个人理解文档就是一条数据一个对象 我们添加索引文档中不仅包含了数据还包含了元数据 比如我们为一个数据添加索引 文档中不仅有jso ...

  4. java陷阱之自动拆箱

    项目中突然报空指针异常 Integer code=null; code=code==null?500:code; 排查发现三元运算符空指针异常,表面上看不出来有什么问题,编译器编译的时候会保证:2边数 ...

  5. 洛谷 P1903 BZOJ 2120 清橙 A1274【模板】分块/带修改莫队(数颜色)(周奕超)

    试题来源 2011中国国家集训队命题答辩 题目描述 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会像你发布如下指令: 1. Q L R代表询问你从第L支画笔 ...

  6. 0120Keeplived实现自动切换Mysql服务

    转自http://biancheng.dnbcw.info/mysql/381020.html Keepalived+mysql 自动切换网络结构:VIP 192.168.88.200mysq11 1 ...

  7. redis--周边知识点

    一般Redis服务器内存超过128G内存的机器是非常少的 很少在redis中缓存视频,除非是快播,一般都是缓存文本字段 redis可视化的工具和SQL的管理工具是不一样的,最好是使用REDIS的she ...

  8. BA-WG-冷源

    冷源群控系统最好由冷源厂家来做的理由 1.冷机厂家对空调的参数十分的清楚,明确的知道冷机的负荷曲线,可以优化冷机加减载的最合理时间达到最佳的节能效果 2.独立的CSM硬件模块,内置不同冷机的型号特性, ...

  9. spring-logback

    <?xml version="1.0" encoding="UTF-8"?><!-- 说明: 1.日志级别及文件 日志记录采用分级记录,级别与 ...

  10. 轻快的vim(一):移动

    断断续续的使用VIM也一年了,会的始终都是那么几个命令,效率极低 前几个星期把Windows换成了Linux Mint,基本上也稳定了下来 就今晚,我已经下定决心开始新的VIM之旅,顺便写一系列的笔记 ...