autolayout中的Content Hugging Priority 和 Compression Resistance Priority 属性使用
autolayout中的Content Hugging Priority 和 Compression Resistance Priority 属性使用
背景
autolayout作为苹果官方的界面布局方式, 自成体系, 虽然被诟病许多, 但是考虑到兼容性和稳定性上, 在不严格要求性能的场景中还是使用比较广泛的, 尤其是多设备界面适配中, 几乎成为不得不用的布局方式.
相比较传统的frame布局方式, 在面对更多的设备尺寸参数情况下, frame无法适应动态尺寸的变化, 如果不得不用, 只能面对frame布局带来的逻辑复杂度提升, 和界面稳定性的降低.
autolayout 本质是通过描述两个view对象之间的约束构成一个方程, 一个完整的界面由多个约束构成一个线性方程组, 通过引擎计算这个方程组从而得到每个view对象的frame, 最终渲染的时候还是使用的计算出来的坐标, 这个和任何界面布局的原理都是类似的.
本文描述autolayout中有关优先级和拉伸压缩相关的内容.
autolayout约束
一个约束描述的是两个view对象之间的布局关系
比如下面的代码:
NSLayoutConstraint(item: myView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1.0, constant: 2.0).isActive = true
上面的代码表示 myview的.trailing 和 view的trailingMargin 之间的约束关系
myview.trailing = view.trailingMargin * 1.0 + 2.0
想象一下, 如果一个界面中存在多个约束互相冲突的时候, 这个时候怎么解决, 比如
一条约束要求view的height是100, 一个约束要求view的height大于等于120, 这个时候针对每一条约束关系, 有了一个优先级的概念. 当发生冲突的时候, 高优先级的约束关系先得到满足.
autolayout约束优先级
布局的优先级是一个结构体, 可以使用float值初始化,范围在 0 ~ 1000之间
struct UILayoutPriority
其自身内置了几个优先级的定义
+----------------------------------+-------+
| Constant | Value |
+----------------------------------+-------+
| UILayoutPriorityRequired | 1000 |
| UILayoutPriorityDefaultHigh | 750 |
| UILayoutPriorityDefaultLow | 250 |
| UILayoutPriorityFittingSizeLevel | 50 |
+----------------------------------+-------+
其中
UILayoutPriorityRequired 表示最高优先级, 必须满足
UILayoutPriorityDefaultLow 表示默认低优先级
常见的优先级如下:
正常约束的默认优先级是 UILayoutPriorityRequired
拉伸和压缩的优先级, H表示水平方向, V表示垂直方向
+-------------------------+---------------+------------------------------+
| Object | Hugging (H,V) | Compression Resistance (H,V) |
+-------------------------+---------------+------------------------------+
| UIActivityIndicatorView | 750,750 | 750,750 |
| UIButton | 250,250 | 750,750 |
| UIDatePicker | 250,250 | 750,750 |
| UIImageView | 251,251 | 750,750 |
| UILabel | 251,251 | 750,750 |
| UIPageControl | 250,250 | 750,750 |
| UIPickerView | 250,250 | 750,750 |
| UIProgressView | 250,750 | 750,750 |
| UIScrollView | 250,250 | 750,750 |
| UISearchBar | 250,250 | 750,750 |
| UISegmentedControl | 250,250 | 750,750 |
| UISlider | 250,250 | 750,750 |
| UIStepper | 750,750 | 750,750 |
| UISwitch | 750,750 | 750,750 |
| UITabBar | 250,250 | 750,750 |
| UITextField | 250,250 | 750,750 |
| UITextView | 250,250 | 750,750 |
| UIToolbar | 250,250 | 750,750 |
| UIView | 250,250 | 750,750 |
+-------------------------+---------------+------------------------+
Content Hugging Priority 和 Compression Resistance Priority 如何使用
Content Hugging Priority 和 Compression Resistance Priority 是作为UIView的一个属性, 默认每一个UIView对象都存在已经有的默认值, 通过下面的API可以读取或者修改
/// 设置拉伸优先级
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis API_AVAILABLE(ios(6.0));
/// 设置抗压缩优先级
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis API_AVAILABLE(ios(6.0));
CCompression Resistance Priority 值越小越容易被压缩
使用XCode编辑器编辑下面结构的视图
我们设置容器的高度是200, A高度是150, B高度是80, 因此容器在垂直方向是不能够完整容纳下A和B的
其约束设置分别如下
容器
A
B
运行之后得到结果:
同时控制台输出
2021-09-10 18:30:11.913198+0800 ContentHugging[29261:13363472] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x6000008158b0 UILabel:0x7ffc15415df0.height == 150 (active)>",
"<NSLayoutConstraint:0x6000008159f0 UILabel:0x7ffc15416680.height == 80 (active)>",
"<NSLayoutConstraint:0x600000815a90 UIView:0x7ffc15415c80.height == 200 (active)>",
"<NSLayoutConstraint:0x600000815b30 V:|-(0)-[UILabel:0x7ffc15415df0] (active, names: '|':UIView:0x7ffc15415c80 )>",
"<NSLayoutConstraint:0x600000815cc0 V:[UILabel:0x7ffc15416680]-(0)-| (active, names: '|':UIView:0x7ffc15415c80 )>",
"<NSLayoutConstraint:0x600000815d10 V:[UILabel:0x7ffc15415df0]-(0)-[UILabel:0x7ffc15416680] (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x6000008158b0 UILabel:0x7ffc15415df0.height == 150 (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
可以看到A被压缩了, 本来设置的高度是150, 现在实际渲染生成的是120;
同时系统警告这里的约束存在冲突, 描述了高度存在冲突的信息
尝试改变B高度的优先级为low, 看下效果
可以看到这次压缩的是B, 本来80,现在实际高度是50
我们在上面的基础之上, 降低一下A的抗压缩优先级为low, 发现压缩的还是B
我们将B的高度约束优先级恢复为1000, 同时改变A的抗压缩等级为low 250
可以看到这次压缩的是A了
总结以下几点
在抗压缩等级相同的时候, 如果降低高度优先级的约束, 同样可以实现压缩指定view的结果
在高度优先级不对等的时候, 修改抗压缩等级可能达不到预期的效果
Content Hugging Priority 值越小越容易被拉伸
我们将容器的高度改为400, 在A和B的高度约束优先级相等; A和B的抗拉伸优先级相等的时候, 默认拉伸的是A
降低B的高度优先级,A和B的抗拉伸优先级相等的时候, 拉伸的是B
A和B的高度约束优先级相等, 将B的拉伸优先级降低, 拉伸的还是A, 这一点特性和抗压缩有点buyiyng
抗拉伸的特性配合高度的约束会更加明确一些
一个合并UISCrollview复杂的例子
import UIKit
import SnapKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let compressContainer = UIView(frame: .zero)
compressContainer.backgroundColor = .gray
view.addSubview(compressContainer)
compressContainer.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(150)
make.height.equalTo(180)
}
let scrollview = UIScrollView(frame: .zero)
scrollview.backgroundColor = .green
compressContainer.addSubview(scrollview)
scrollview.snp.makeConstraints { make in
make.left.top.right.equalToSuperview()
make.width.equalTo(150)
make.height.equalTo(150)
}
scrollview.setContentCompressionResistancePriority(.required, for: .vertical)
let scrollviewSubContainer = UIView(frame: .zero)
scrollviewSubContainer.backgroundColor = .yellow
scrollview.addSubview(scrollviewSubContainer)
scrollviewSubContainer.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
let textContent = UIView(frame: .zero)
textContent.backgroundColor = .red
scrollviewSubContainer.addSubview(textContent)
textContent.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.width.equalTo(150)
make.height.equalTo(200)
}
let bottomView = UIView(frame: .zero)
bottomView.backgroundColor = .purple
compressContainer.addSubview(bottomView)
bottomView.snp.makeConstraints { make in
make.top.equalTo(scrollview.snp.bottom)
make.left.right.equalToSuperview()
make.height.equalTo(50).priority(.low)
make.bottom.equalToSuperview()
}
}
}
效果
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架