NSLayoutConstraint的使用

*一切皆代码*
- --

#继承关系
框架|类|类
:-:|:-:|:-:
UIKit|NSLayoutConstraint|-
-|-|-

#应用场景
UI界面的搭建一般会占用项目开发相当一部分的时间。涉及到控件布局,控件配置,人机交互,动画效果,数据显示,屏幕适配6个方面,还要考虑视觉效果,性能体验,数据边界(没有数据/很多数据),操作防御(各种狂点)4个方面。另外,UI界面也是开发过程中需求变化比较多的地方。 其中控件布局是最基础和根本的一个方面。

布局,顾名思义,就是设置控件在屏幕上的位置。自动是相对之前的手动计算控件坐标(frame)布局而言的。NSLayoutConstraint描述某控件与其他控件的位置关系,再由布局引擎在恰当的时候自动计算出该控件的坐标,进而准确的显示到屏幕上。这种位置关系称为约束。当某些变化发生时,布局引擎会根据这些约束自动的调整控件的位置大小。

这些变化主要来自两个方面:
外因:屏幕的不同尺寸,屏幕的旋转,父视图的改变,用户行为和视图逻辑
内因:显示的内容(text,image),字体的调整

目前,除了原生的NSLayoutConstraint,GitHub上比较主流的自动布局框架还有masonry和SDAutoLayout。masonry相当于原生框架的封装,语法更简洁。SDAutoLayout提供了cell高度自适应等便捷方法。

# 概念原理
- 坐标frame
屏幕采用的是二维坐标系,原点在左上角(0.0),向右x坐标增大,向下y坐标增大。靠近原点偏移时偏移量为负,远离原点偏移时偏移量为正。坐标单位是点point,也是代码中的布局单位。

- 约束constraint
一条约束相当于一个线性方程:
item1.attribute1 [=,<=,>=] multiplier * item2.attribute2 + constant
一条约束包含5种元素:视图item,属性attribute,关系relationship,乘因子multiplier,常量constant。一个视图有水平垂直两个方向,每个方向需要两个约束确定,即四个约束确定一个视图的位置大小。约束多了,冲突;约束少了,歧义,都会无法正常显示甚至crash。

-  属性attribute
描述位置:top,bottom,left,right,centerX,centerY,leading,trailing
描述尺寸:wight,height
水平方向:left,right,centerX,wight,leading,trailing
垂直方向:top,bottom,centerY,height

- 关系relation
相当关系 =
不等关系 >=,<=

- 乘因子multiplier
倍数关系

- 常量constant
偏移量,注意偏移方向和正负值

- 优先级priority
用来描述约束的优先级。数值是1-1000。1000表示约束是必须满足的,其他值表示可选的。约束的优先级默认是必须的(1000)。系统定义了一些字符常量来表示必须,高,低,适应尺寸等优先级,由高到低。不应该显示使用必须优先级,不应该随意指定优先级数值,常用取值999,750,500,250,左右浮动一两个点。尽量使用系统定义的字符常量。布局引擎统筹全局,将首先满足必须的约束。可选约束,能满足就满足,不能满足也会尽量靠近。所以,不能满足的可选约束也会影响最后的显示效果。

- 固有内容尺寸intrinsic content size
固有内容尺寸可以理解为默认尺寸。一个控件的内容尺寸由有无显示的内容,内容内边距,可否滑动,有无外在约束决定。核心就是显示的内容。一般能显示内容的控件都有内容尺寸。控件根据内容自适应,父视图根据子视图布局自适应都基于这个概念。具有固有内容尺寸的控件,可以不约束宽或高从而直接使用固有内容尺寸。

控件|固有内容尺寸
:-:|:-:
UIView|没有固有尺寸
UILabel|宽高都有固有尺寸
UIButton|宽高都有固有尺寸
UITextField|宽高都有固有尺寸
UISwitch|宽高都有固有尺寸,且应该使用固有尺寸(不要约束宽高)
UIImageView|无内容时没有固有尺寸,有内容后,宽高都有固有尺寸
UITextView|若能滑动,没有;若不能滑动,默认以将字符串单行显示时的size为固有尺寸;若不能滑动,且约束了宽度,则自动调整高度,来显示全部字符串,以此时的size作为固有尺寸。

- CHCR约束
CHCR是一对针对固有尺寸的约束,有固有尺寸的视图水平,垂直两个方向都有一对CHCR约束。使用CHCR需要管理其优先级。
CH,content hugging,内容抱紧(压缩)
相当于view.width <= 0.0 * nil.NotAnAttribute + intrinsicwidth
CR,content compression resistance,内容压缩阻尼(伸展)
相当于view.width >= 0.0 * nil.NotAnAttribute + intrinsicwidth

- 固有内容尺寸 intrinsic content size 与 视图size自适应
当有一个视图supview,使用constraint布局它的subviews。若subviews之间的约束链能明确的确定它们占用的size,就相当于supview的intrinsic content size确定了,系统会自动的计算出intrinsic content size作为supview的size。这时,只需要对supview添加约束,确定其位置就可以了。这种逻辑在水平或垂直某个方向上也是成立的。这就是视图自适应的原理。父视图自适应三个关键点:
1 . 子视图不要以父视图自适应的那个边为参照。若父视图宽度自适应,就不要以右边为参照,若父视图高度自适应,就不要以底边为参照。
2 . 子视图的约束链要能明确的确定这些子视图的总的宽(水平方向)或高(水平方向)
3 . 若父视图是宽度自适应,需要添加一个与最右边的子视图的约束;若父视图是高度自适应,需要添加一个与最下边的子视图的约束(我简称为兜底)


# 代码流程
- 布局的具体流程
1 . 创建控件(alloc,init或new)
2 . 添加到父视图(addSubview)
3 . 配置控件属性
4 . 关闭转换(setTranslatesAutoresizingMaskIntoConstraints)
5 . 创建约束(一般每个控件4个约束)
6 . 激活约束(添加约束到最近公共父视图)
7 . 更新约束/取消激活约束(移除约束)

- 添加子视图
```
[self.view addSubviews:@[_scroll]];
```
- 关闭转换
```
[_scroll setTranslatesAutoresizingMaskIntoConstraints:NO];
```
- 创建约束
```
NSLayoutConstraint * scroll_top = [NSLayoutConstraint constraintWithItem:_scroll
                                                                   attribute:NSLayoutAttributeTop
                                                                   relatedBy:NSLayoutRelationEqual
                                                                      toItem:self.view
                                                                   attribute:NSLayoutAttributeTop
                                                                  multiplier:1.0f
                                                                    constant:0];
NSLayoutConstraint * scroll_left = [NSLayoutConstraint constraintWithItem:_scroll
                                                                    attribute:NSLayoutAttributeLeft
                                                                    relatedBy:NSLayoutRelationEqual
                                                                       toItem:self.view
                                                                    attribute:NSLayoutAttributeLeft
                                                                   multiplier:1.0f
                                                                     constant:0];
NSLayoutConstraint * scroll_botton = [NSLayoutConstraint constraintWithItem:_scroll
                                                                      attribute:NSLayoutAttributeBottom
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:self.view
                                                                      attribute:NSLayoutAttributeBottom
                                                                     multiplier:1.0f
                                                                       constant:0];
NSLayoutConstraint * scroll_right = [NSLayoutConstraint constraintWithItem:_scroll
                                                                     attribute:NSLayoutAttributeRight
                                                                     relatedBy:NSLayoutRelationEqual
                                                                        toItem:self.view
                                                                     attribute:NSLayoutAttributeRight
                                                                    multiplier:1.0f
                                                                      constant:0];

```
特别的:
```
NSLayoutConstraint * top = [NSLayoutConstraint constraintWithItem:view
                                                                   attribute:NSLayoutAttributeWight
                                                                   relatedBy:NSLayoutRelationEqual
                                                                      toItem:nil
                                                                   attribute:NSLayoutAttributeNotAnAttribute
                                                                  multiplier:0.0
                                                                    constant:100];
```
- 添加约束
注意:约束必须添加到所有涉及到的item的最近公共父视图。
```
[self.view addConstraints:@[scroll_top,scroll_left,scroll_botton,scroll_right]];
```
或者(推荐)
```
[NSLayoutConstraint  activateConstraints:@[scroll_top,scroll_left,scroll_botton,scroll_right]];
```
- 移除约束
```
[self.view removeConstraints:@[scroll_top,scroll_left,scroll_botton,scroll_right]];
```
或者(推荐)
```
[NSLayoutConstraint  deactivateConstraints:@[scroll_top,scroll_left,scroll_botton,scroll_right]];
```
- 更新约束
改变约束的情况:
改变约束的activate(相当于添加/移除)
改变约束的constant(主要,直接在需要改变的地方某个已有约束的constant 属性即可)
改变约束的priority
移除视图(移除视图会移除与之相关的所有约束)
- 相关方法
view的方法
```
[view updateConstraintsIfNeeded];//立即更新view及其子视图的约束
[view layoutIfNeeded];//立即更新view及其子视图的位置尺寸
```
```
[view setNeedsUpdateConstraints];//延时更新约束,系统自动对所有VC调用updateViewConstraints方法,对所有View调用updateConstraints方法;VC 重写updateViewConstraints方法,View重写updateConstraints。例:
- (void)updateConstraints{
//这里设置约束
[super updateConstraints];//必须在后面调用父类该方法。
}

[view setNeedsLayout];//延时更新位置尺寸,系统自动对所有VC调用viewWillLayoutSubviews方法,对所有view调用layoutSubviews方法。重写layoutSubviews方法,例:
- (void)layoutSubviews{
//这里最后设置坐标frame(不建议)
[super layoutSubviews];//必须在后面调用父类该方法。
}
```
```
CGSize size = [view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];//根据约束返回size
CGSize size = [view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize  withHorizonalFittingPriority:UILayoutPriorityFittingSizeLevelverticalFittingPriority:UILayoutPriorityFittingSizeLevel];//根据约束,水平垂直优先级返回size
```
```
[view setContentHuggingPriority:UILayoutPriorityFittingSizeLevel forAxis:UILayoutConstraintAxisHorizontal];//设置某个方向的CH优先级
[view setContentCompressionResistancePriority:UILayoutPriorityFittingSizeLevel forAxis:UILayoutConstraintAxisHorizontal];//设置某个方向的CR优先级
```
```
[view sizeThatFit:currentbounds];//不修改view的size,返回view的size
[view sizeToFit:currentbounds];//调用sizeThatFit方法,会修改view的size,返回view的size。
```
label的方法
```
label.numberOfLines = 0;//显示不限行数,label高度自适应需要
label.preferredMaxLayoutWidth = 180;//label高度自适应需要
```
```
CGFloat height = label.font.lineHeight;//label的单行高度
```
```
CGRect titleRect = [titleLabel.text boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width - 20, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil];//根据text,font等信息返回label的size
```
# 使用原则
- 创建约束
1. 先添加到父视图,后添加约束(先添加后约束)
2. size属性(wight,height)不能对location属性(top,bottom,left,right,centerX,centerY)
水平属性(left,right,centerX)不能对垂直属性(top,bottom,centerY)。height可对wight。
3. left/right不能对leading/trailing
4. 不能只将constant分配给location(top,botton,left,right),centerX,centerY可以。
5. locaton(top,botton,left,right)中的multiplier必须为1.0。centerX,centerY不必。

- 添加约束
1.必须添加到约束涉及到的所有item的最近父视图。若用下面方法添加,系统自动添加的正确的视图。
```
[NSLayoutConstraint  activateConstraints:@[scroll_top,scroll_left,scroll_botton,scroll_right]];
```

- CHCR
1. 当一列view填充一个空间,若每个view有相同的CH优先级,系统将不知道拉伸哪个viw。此时,应将需要拉伸的view的CH优先级降低。实际,在IB中,每个UILable的CH优先级会自动设为251。
2. 对于背景不可见的view,如lable,button。它们经常会被莫名其妙的拉伸,导致显示的内容的位置稍有偏差。为防止这种情况,将其CH的优先级升高。
3. 有些视图,如swith,就应该按照intrinsec content size 显示,将其CH,CR的优先级都升高。
4. 尽量不要给CHCR优先级设为required(1000)。若想让一个view按照它的intrinsic content size显示,将它的CHCR优先级设为(999)。

# 代码demo
- label的宽度自适应
```
```
- label的高度自适应
```
```
- 父视图size固定的多个label子视图
- 父视图高度自适应的多个label子视图
- 父视图宽度自适应的多个label子视图
- 父视图size自适应的多个label子视图
- scrollview的contentsize自适应
- tableviewcell的高度自适应

# 报错记录
>错误描述:
错误码:
代码环境:
原因:
解决方案:

转载于:https://www.cnblogs.com/gt1989/p/7641935.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值