Auto Layout压缩阻力及内容吸附讲解
zhezhongyun 2025-07-03 20:52 26 浏览
本文为投稿文章,作者:梁炜V
在Auto Layout的使用中,有两个很重要的布局概念:Content Compression Resistance和Content Hugging,从字面的翻译我们大概可以分别翻译为:压缩阻力以及内容吸附。但是光从字面意思来理解很难知道它们如何使用以及确切的设计意图。我最开始也是很迷糊而且在使用Auto Layout的过程中也没有使用过它们,直到最近稍稍研究了一下,发现它们的作用甚是巨大,所以我为了加深记忆,把我最近学习到的关于它们的概念在此稍作整理加以记录。
> 注:以下为了表述方便,将`Content Compression Resistance`记为`压缩阻力`,将`Content Hugging`记为`内容吸附`。
Content Compression Resistance
压缩阻力属性为了记忆更加形象我们可以把它理解为离我远点,不许挤到我,它的优先级(Priority)越高,它的这种抗挤压的能力也就越强,我们可以通过代码在控件的水平或垂直方向上分别设置1(最低优先级)到1000(最高优先级)之间的优先级,默认是750,例如我们可以为一个UILabel控件设置一个它在水平方向上优先级为500的压缩阻力:
[labelsetContentCompressionResistancePriority:500forAxis:UILayoutConstraintAxisHorizontal];
用图来表示,我们可以大致表示如下,视图的压缩阻力就好似它自身往外的张力,优先级越高,视图自己维持自身显示完整性的能力就越强:
Content Hugging
内容吸附属性为了记忆方便我们可以把它理解为抱紧(Hug),视图的大小不会随着superView的变大而扩大,而是只维持能完全显示自己内容的大小,它的这种优先级越高,吸附的能力就越强,和压缩阻力一样,内容吸附的优先级也可以通过代码来设置,只是它的默认优先级是250:
[labelsetContentHuggingPriority:251forAxis:UILayoutConstraintAxisHorizontal];
用图来表示,我们可以大致表示如下,视图的内容吸附就好似视图自己有向内抱紧自己的力量一样,优先级越高,它的这种能力就越强:
以上讲了内容吸附和压缩阻力的基本概念,但是这两个属性是建立在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中添加一个我们自定义的视图,设置它水平居中,顶部和父视图对齐。
-(void)layoutSubIntrinsicView
{
IntrinsicView*intrinsicView=[IntrinsicViewnew];
intrinsicView.backgroundColor=[UIColorcolorWithRed:.2green:.4blue:.6alpha:1];
[self.viewaddSubview:intrinsicView];
[intrinsicViewmas_makeConstraints:^(MASConstraintMaker*make){
make.centerX.mas_equalTo(self.view);
make.top.mas_equalTo(self.mas_topLayoutGuideBottom);
}];
}运行后发现什么也没显示,因为我们没有设置它的宽高,而它默认的固有尺寸是(-1 ,-1)。我们去重写IntrinsicView的- (CGSize)intrinsicContentSize方法:
@implementationIntrinsicView
-(CGSize)intrinsicContentSize
{
returnCGSizeMake(150,66);
}
@end运行后显示如下:
1、测试内容吸附优先级
为了测试内容吸附优先级我们在页面上添加两个IntrinsicView,分别是topView和bottomView,设置他们都水平居中,然后分别和页面的顶部和底部对齐:
-(void)layoutSubIntrinsicView
{
IntrinsicView*topView=[IntrinsicViewnew];
topView.backgroundColor=[UIColorcolorWithRed:.2green:.4blue:.6alpha:1];
[self.viewaddSubview:topView];
[topViewmas_makeConstraints:^(MASConstraintMaker*make){
make.centerX.mas_equalTo(self.view);
make.top.mas_equalTo(self.mas_topLayoutGuideBottom);//和导航栏底部对齐
}];
IntrinsicView*bottomView=[IntrinsicViewnew];
bottomView.backgroundColor=[UIColorcolorWithRed:.2green:.4blue:.6alpha:1];
[self.viewaddSubview:bottomView];
[bottomViewmas_makeConstraints:^(MASConstraintMaker*make){
make.centerX.mas_equalTo(self.view);
make.bottom.mas_equalTo(self.mas_bottomLayoutGuideBottom);//和页面底部对齐
}];
}运行后展示如下:
下面我们设置topView和bottomView之间的间距为40,也就是topView.bottom + 40 = bottomView.top。
-(void)layoutSubIntrinsicView
{
IntrinsicView*topView=[IntrinsicViewnew];
topView.backgroundColor=[UIColorcolorWithRed:.2green:.4blue:.6alpha:1];
[self.viewaddSubview:topView];
[topViewmas_makeConstraints:^(MASConstraintMaker*make){
make.centerX.mas_equalTo(self.view);
make.top.mas_equalTo(self.mas_topLayoutGuideBottom);//和导航栏底部对齐
}];
IntrinsicView*bottomView=[IntrinsicViewnew];
bottomView.backgroundColor=[UIColorcolorWithRed:.2green:.4blue:.6alpha:1];
[self.viewaddSubview:bottomView];
[bottomViewmas_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函数的最后添加如下语句:
[topViewsetContentHuggingPriority:251forAxis:UILayoutConstraintAxisVertical];
运行后如下所示,达到了我们想要的效果:
>251是我随意定的比250大的值,可以是大于250小于1000的任何值。
2、测试压缩阻力优先级
我们通常会遇到如下图所示的需求:
在某个页面上水平放置两个UILabel,leftLabel的左边和父视图的间距固定,rightLabel的右边和父视图的右边有一个小于等于某个间隔的约束,leftLabel和rightLabel之间有一个固定间距,它们的宽度根据他们显示的内容自适应,关键代码如下:
[leftLabelmas_makeConstraints:^(MASConstraintMaker*make){
//左边和父视图间隔固定为10
make.left.mas_equalTo(self.view).offset(10);
make.top.mas_equalTo(80);//随意设定的值
}];
[rightLabelmas_makeConstraints:^(MASConstraintMaker*make){
make.top.mas_equalTo(leftLabel);
//和leftLabel的右边间距固定为20
make.left.mas_equalTo(leftLabel.mas_right).offset(20);
//这里注意是‘lessThanOrEqualTo’,也就是‘rightLabel’的右边界
//和父视图的间距至少为10,内容少时,间距自动调大
make.right.mas_lessThanOrEqualTo(self.view).offset(-10);
}];在他们的显示内容宽度不超过父视图宽度时,两个label的内容都能正常的完全显示,但是当它们需要显示的内容长度总和超过父视图的宽度时,就会显示如下:
一个label被压缩了, rightLabel显示不完全,如果在这种情况下,我们想leftLabel被压缩,而rightLabel尽量完全显示,由于UILabel这类控件,系统自己已经根据它们显示的实际内容实现了固有尺寸的方法,我们可以利用压缩阻力的特性,将rightLabel的压缩阻力优先级设置得比leftLabel高,上面介绍了压缩阻力的默认优先级是750,我们把`rightLabel`的优先级设置为751,在上面代码的最下面添加如下代码:
[rightLabelsetContentCompressionResistancePriority:751forAxis:UILayoutConstraintAxisHorizontal];
运行后显示如下,达到了我们预期的效果:
3、在自动计算UITableViewCell高度中的使用
对于变高cell的处理,以前我们都是在`heightForRowAtIndexPath`方法里面,拼凑要展示的变高cell的高度,当我们改变cell中两个控件在垂直方向的布局,或者再添加一个控件时,还要去修改计算cell高度的方法来适应新的变化,非常不方便。但是有了自动布局后,利用好`压缩阻力`和`内容吸附`的优先级,可以很精确很简单的由系统来计算出变高cell的高度。
假定我们有如下需求:
我们看到,这个变高cell里面高度不定的是中间的`ContentLabel`,它会根据内容长度来折行显示,UILabel要折行显示我们需要设置它的`preferredMaxLayoutWidth`和`numberOfLines`两个属性的值。
首先假定`Model`的定义如下:
@interfaceCellModel:NSObject @property(nonatomic,copy)NSString*name; @property(nonatomic,copy)NSString*company; @property(nonatomic,copy)NSString*content; @property(nonatomic,assign)CGFloatcacheHeight;//缓存当前Model显示的cell高度 @end
自定义的`UITableViewCell`的关键代码如下:
//图片距左边距离为10,上下居中
[_cellImageViewmas_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);
}];
//标题Label,一行显示
[_nameLabelmas_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);
}];
//内容label,多行显示
_contentLabel.numberOfLines=0;
[self.contentViewaddSubview:_contentLabel];
[_contentLabelmas_makeConstraints:^(MASConstraintMaker*make){
make.left.mas_equalTo(self.nameLabel);
make.top.mas_equalTo(self.nameLabel.mas_bottom).offset(6);
}];
//标题Label,一行显示
[_companyLabelmas_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);//设定了这个自动计算cell高度时才知道具体cell的高度
}];
[_nameLabelsetContentHuggingPriority:UILayoutPriorityRequiredforAxis:UILayoutConstraintAxisVertical];
[_companyLabelsetContentHuggingPriority:UILayoutPriorityRequiredforAxis:UILayoutConstraintAxisVertical];
[_contentLabelsetContentHuggingPriority:UILayoutPriorityRequiredforAxis:UILayoutConstraintAxisVertical];上面的代码中设置了几个`UILabel`的`内容吸附`优先级为最高,这样它们就不会随着cell高度的变化而拉伸高度。上面设置了`contentLabel`的`numberOfLines = 0`,还需要设置`preferredMaxLayoutWidth`才能正确换行显示。由于`UITableViewCell`在显示出来之前是不知道宽度的,但是为了获取正确的宽度我们可以在`- (void)layoutSubviews`方法里面设置:
-(void)layoutSubviews
{
_contentLabel.preferredMaxLayoutWidth=CGRectGetWidth(self.contentView.frame)-128-10-6;//后面的数字是前后以及图片的宽度
[superlayoutSubviews];//这个调用是为了改变后更新布局
}这样我们设置好cell以及Model以后,其他的方法都和普通的使用一样,唯一不一样的就是计算cell高度的`UITableView`代理方法`heightForRowAtIndexPath`,它的实现如下:
-(CGFloat)tableView:(UITableView*)tableViewheightForRowAtIndexPath:(NSIndexPath*)indexPath
{
staticCodeLayoutCell*singleCell=nil;
staticdispatch_once_tonceToken;
dispatch_once(&onceToken,^{
//这里持有一个cell是为了下面自动计算cell高度的需要
singleCell=[tableViewdequeueReusableCellWithIdentifier:kCellIdentifier];
});
//取出Model,如果有缓存的高度值就不计算了
CellModel*model=_dataSourceArray[indexPath.row];
if(model.cacheHeight!=0){
returnmodel.cacheHeight;
}
[singleCelllayoutIfNeeded];//强制布局,得到contentView的宽度
[singleCellsetNewCellModel:model];
//由系统根据我们设定的Layout规则来计算cell显示的Size
CGSizesize=[singleCell.contentViewsystemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
model.cacheHeight=size.height+1;//cell和cell.contentView的高度相差1
returnmodel.cacheHeight;
}运行的效果如下:
当图片的高度大于三个`UIlabel`加上各自上下的间隔的高度时,由于我们设置了三个Label的`内容吸附`最高的优先级,所以为了满足它们的高度,图片的内容就进行了压缩,如下:
第二个cell的图片被压缩了,如何才能保证它不被压缩呢?留给读到这里的人自己实现吧!????
暂时先写到这里吧,由于刚接触这两个属性,难免会有遗误之处,请大家多多谅解!
完整的Demo,请戳[这里]
相关推荐
- Python入门学习记录之一:变量_python怎么用变量
-
写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...
- python变量命名规则——来自小白的总结
-
python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...
- Python入门学习教程:第 2 章 变量与数据类型
-
2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...
- 绘制学术论文中的“三线表”具体指导
-
在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...
- Python基础语法知识--变量和数据类型
-
学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...
- 一文搞懂 Python 中的所有标点符号
-
反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...
- Python变量类型和运算符_python中变量的含义
-
别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...
- 从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序
-
在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...
- Python中下划线 ‘_’ 的用法,你知道几种
-
Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...
- 解锁Shell编程:变量_shell $变量
-
引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...
- 一文学会Python的变量命名规则!_python的变量命名有哪些要求
-
目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...
- 更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for
-
src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...
- C++第五课:变量的命名规则_c++中变量的命名规则
-
变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....
- Rust编程-核心篇-不安全编程_rust安全性
-
Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...
- 探秘 Python 内存管理:背后的神奇机制
-
在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- opacity 属性 (32)
- transition 属性 (33)
- 1-1. 变量声明 (31)
