前言
第四篇来了。 还是3个小例子,仍然是主要部分用Masonry手写代码实现,其它的约束在storyboard里面直接拖拽搭建。 三个例子分别是:
- 用约束优先级保证可移动View的内容可见
- Autolayout的约束=控件间位置关系的“绑定”
- 利用
layoutIfNeeded控制约束的生效时机
前三篇:
Github地址: https://github.com/zekunyan/AutolayoutExampleWithMasonry

Case 1: 用约束优先级保证可移动View的内容可见性
实现功能
Case 1实现的功能就是,可以用手指拖动一个灰色的Tip控件,在红框containerView内移动,并且保持移动到红框边缘时,Tip控件的内容不会超出红框,保证内容的可见性。
Frame VS Autolayout
如果不用Autolayout的话,直接计算、设定frame,那么可能的步骤如下:
- 获取手指当前触摸点在红框containerView内的坐标
- 计算Tip控件的宽高
- 根据宽高,计算、判断Tip控件会不会在新的位置上超出边界
- 调整新的Tip位置坐标
- 设置Tip控件的frame,指定新的坐标
这样去实现的话,代码将会比较“丑”=。。=,有大量的判断、数值计算,而且容易出错。
而用Autolayout的话,只需要两步:
- 设置约束
- 根据手指坐标,更新left、top两个约束,即更新Tip的位置
至于保证Tip内容在可见范围内,就交给Autolayout处理吧~
关键原理
用Autolayout实现的关键,就是善于利用约束的优先级。先看看设置的约束示意图:

中间的tipLabel就是被移动的控件
- 褐色的两个约束,分别是Tip控件的centerX和centerY,与上级View的left和top的约束,用来对Tip定位
- 四个蓝色的约束,则是确保Tip内容不超出边界的约束
重点其实就在于,蓝色的约束的优先级比褐色的优先级高,这样,在移动中,系统会优先满足Tip的内容保持在边界内。
实现细节
具体的设置约束代码如下:
左边和顶部的约束 - 控制位置
首先设置两个属性,保存左边和顶部的约束,用于在移动时调整位置:
@property (nonatomic, strong) MASConstraint *leftConstraint;
@property (nonatomic, strong) MASConstraint *topConstraint;
然后就是设置具体的left、top约束,并设置优先级为750,也就是High
[_tipLabel mas_makeConstraints:^(MASConstraintMaker *make) {
// 优先级要比边界条件低
_leftConstraint = make.centerX.equalTo(_containerView.mas_left).with.offset(50).priorityHigh();
// 优先级要比边界条件低
_topConstraint = make.centerY.equalTo(_containerView.mas_top).with.offset(50).priorityHigh();
// ...
}];
四周的约束 - 保证内容在边界内
然后就是上下左右四个边界的约束:
[_tipLabel mas_makeConstraints:^(MASConstraintMaker *make) {
// 边界条件约束,保证内容可见,优先级1000
make.left.greaterThanOrEqualTo(_containerView.mas_left);
make.right.lessThanOrEqualTo(_containerView.mas_right);
make.top.greaterThanOrEqualTo(_containerView.mas_top);
make.bottom.lessThanOrEqualTo(_containerView.mas_bottom);
// ...
}];
这个地方要注意是lessThanOrEqualTo还是greaterThanOrEqualTo。
滑动时更新Tip位置
这一点比较简单,直接将当前触摸的坐标赋值给leftConstraint和rightConstraint就好:
_leftConstraint.offset = touchPoint.x;
_topConstraint.offset = touchPoint.y;
小节
对比起来,明显是用Autolayout的方法比计算设置frame的方法要“优雅”的多=。=,灵活运用约束的优先级,往往可以减少很多工作量。
Case 2: Autolayout的约束=控件间位置关系的“动态绑定”
实现功能
Case 2的功能其实是在Case 1的基础上实现的,就是在移动一个View的时候,另一个Attachment附件View,始终保持跟这个View的相对位置不变,就像是“附着”、“捆绑”在这个View上一样。
关键概念:约束 = “动态的绑定”
之所以写这个例子,主要是想从概念上更加深入的理解Autolayout与传统的设置Frame的不同,即Autolayout里面的约束,其实是“动态的绑定”,是控件相互之间的位置关系绑定,约束是可以实时保证这种绑定关系的。
传统的设置Frame,在View的内容、位置要变化时,是要手动设置的,是“一次性”的,而Autolayout,在确定了约束后,会实时的用约束的规则去更新Frame,所以可以避免这种手动设置。
所以,对于本例子来说,设置Frame的方式和Autolayout的方式对比如下:

用Autolayout的话,只要把Attachment的位置设置为相对于TipLabel就可以了。
实现细节
代码比较简单,如下:
[attachmentView mas_makeConstraints:^(MASConstraintMaker *make) {
// 依附在tipLabel上
make.left.equalTo(_tipLabel.mas_left).with.offset(20).priorityHigh();
make.bottom.equalTo(_tipLabel.mas_top).with.offset(-2).priorityHigh();
}];
移动Tip的相关代码看Case 1的就行~
小节
本小节的Case比较简单,主要是为了着重点出“Autolayout约束=动态位置绑定”这个点,理解了这一点,才能更好地运用Autolayout~
Case 3: 利用layoutIfNeeded控制约束的生效时机
实现功能
Case 3的例子,是一个灰色的View,初始时在屏幕中央,然后按下“执行动画”后,从左边滑入。
步骤分析
直观上看,感觉是在按下按钮时,修改View的水平方向上的某个约束,然后用UIView的动画执行变化就好了。
但是仔细分析,应该是先设置View的位置到屏幕的左边看不见的区域,然后用动画平滑的再让View移动回原位。
就是说,只是从左边移动回来时,才让动画生效!问题就变成了,如何让约束分段执行,或者说,控制约束的生效时机。
步骤可以由下图表示:

实现关键
步骤分析清楚了,那么问题就是如何让约束在修改后,“立即”生效,而不是等到系统自动在下次更新布局时被动的生效。
答案就是:利用**layoutIfNeeded**
在更改了约束后,调用layoutIfNeeded,就可以通知系统立即刷新布局,从而更新Frame。如果要更加“保险”,可以在调用layoutIfNeeded前,加上调用setNeedsUpdateConstraints、setNeedsLayout,系统刷新布局时,就会先更新约束,然后根据约束重新计算相关View的Frame,想了解具体的过程的,可以参考:Mysteries of Auto Layout, Part 1 - WWDC 2015、Mysteries of Auto Layout, Part 2
实现细节
具体的实现代码如下:
// 设置初始状态,移动View到屏幕左边不可见区域
_centerXConstraint.equalTo(@(-CGRectGetWidth(self.view.frame)));
// 立即让约束生效,刷新Frame
[self.view layoutIfNeeded];
// 设置要动画的约束,移回原位
_centerXConstraint.equalTo(@0);
// 执行动画
[UIView animateWithDuration:0.3f animations:^{
[self.view layoutIfNeeded];
}];
小节
之前在实现TTGSnackbar的时候,就大量的使用了layoutIfNeeded来立即刷新View的Frame,然后才让产生动画的约束变动生效,也就是分阶段的让约束生效,非常管用~
总结
不知不觉已经第四篇了,一共12个Case,都是平时开发时总结出来的点,以及对Autolayout的理解,希望可以一直将这个系列坚持下去~~