有趣的Autolayout示例4-Masonry实现

前言

第四篇来了。
还是3个小例子,仍然是主要部分用Masonry手写代码实现,其它的约束在storyboard里面直接拖拽搭建。
三个例子分别是:

  1. 用约束优先级保证可移动View的内容可见
  2. Autolayout的约束=控件间位置关系的“绑定”
  3. 利用layoutIfNeeded控制约束的生效时机

前三篇:

Github地址:
https://github.com/zekunyan/AutolayoutExampleWithMasonry

Gif示例

Case 1: 用约束优先级保证可移动View的内容可见性

实现功能

Case 1实现的功能就是,可以用手指拖动一个灰色的Tip控件,在红框containerView内移动,并且保持移动到红框边缘时,Tip控件的内容不会超出红框,保证内容的可见性。

Frame VS Autolayout

如果不用Autolayout的话,直接计算、设定frame,那么可能的步骤如下:

  1. 获取手指当前触摸点在红框containerView内的坐标
  2. 计算Tip控件的宽高
  3. 根据宽高,计算、判断Tip控件会不会在新的位置上超出边界
  4. 调整新的Tip位置坐标
  5. 设置Tip控件的frame,指定新的坐标

这样去实现的话,代码将会比较“丑”=。。=,有大量的判断、数值计算,而且容易出错。

而用Autolayout的话,只需要两步

  1. 设置约束
  2. 根据手指坐标,更新left、top两个约束,即更新Tip的位置

至于保证Tip内容在可见范围内,就交给Autolayout处理吧~

关键原理

用Autolayout实现的关键,就是善于利用约束的优先级。先看看设置的约束示意图:

Case 1: 用约束优先级保证可移动View的内容可见性

中间的tipLabel就是被移动的控件

  • 褐色的两个约束,分别是Tip控件的centerX和centerY,与上级View的left和top的约束,用来对Tip定位
  • 四个蓝色的约束,则是确保Tip内容不超出边界的约束

重点其实就在于,蓝色的约束的优先级比褐色的优先级高,这样,在移动中,系统会优先满足Tip的内容保持在边界内

实现细节

具体的设置约束代码如下:

左边和顶部的约束 - 控制位置

首先设置两个属性,保存左边和顶部的约束,用于在移动时调整位置:

1
2
@property (nonatomic, strong) MASConstraint *leftConstraint;
@property (nonatomic, strong) MASConstraint *topConstraint;

然后就是设置具体的left、top约束,并设置优先级为750,也就是High

1
2
3
4
5
6
7
[_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();
// ...
}];

四周的约束 - 保证内容在边界内

然后就是上下左右四个边界的约束:

1
2
3
4
5
6
7
8
[_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位置

这一点比较简单,直接将当前触摸的坐标赋值给leftConstraintrightConstraint就好:

1
2
_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的方式对比如下:

Case 2: Autolayout的约束=控件间位置关系的“绑定”

用Autolayout的话,只要把Attachment的位置设置为相对于TipLabel就可以了。

实现细节

代码比较简单,如下:

1
2
3
4
5
[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移动回原位。

就是说,只是从左边移动回来时,才让动画生效!问题就变成了,如何让约束分段执行,或者说,控制约束的生效时机

步骤可以由下图表示:

Case 3: 利用layoutIfNeeded控制约束的生效时机

实现关键

步骤分析清楚了,那么问题就是如何让约束在修改后,“立即”生效,而不是等到系统自动在下次更新布局时被动的生效。

答案就是:利用layoutIfNeeded

在更改了约束后,调用layoutIfNeeded,就可以通知系统立即刷新布局,从而更新Frame。如果要更加“保险”,可以在调用layoutIfNeeded前,加上调用setNeedsUpdateConstraintssetNeedsLayout,系统刷新布局时,就会先更新约束,然后根据约束重新计算相关View的Frame,想了解具体的过程的,可以参考:Mysteries of Auto Layout, Part 1 - WWDC 2015Mysteries of Auto Layout, Part 2

实现细节

具体的实现代码如下:

1
2
3
4
5
6
7
8
9
10
// 设置初始状态,移动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的理解,希望可以一直将这个系列坚持下去~~

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器