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()
        }
    }
}

效果

posted @   兜兜有糖的博客  阅读(283)  评论(0编辑  收藏  举报
编辑推荐:
· 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 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示