转载

有趣的Autolayout示例4-Masonry实现

第四篇来了。

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

三个例子分别是:

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

前三篇:

  • 第一篇: 有趣的Autolayout示例-Masonry实现
  • 第二篇: 有趣的Autolayout示例2-Masonry实现
  • 第三篇: 有趣的Autolayout示例3-Masonry实现

Github地址:

https://github.com/zekunyan/AutolayoutExampleWithMasonry

有趣的Autolayout示例4-Masonry实现

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实现的关键,就是 善于利用约束的优先级 。先看看设置的约束示意图:

有趣的Autolayout示例4-Masonry实现

中间的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位置

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

_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示例4-Masonry实现

用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移动回原位。

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

步骤可以由下图表示:

有趣的Autolayout示例4-Masonry实现

实现关键

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

答案就是:利用 layoutIfNeeded

在更改了约束后,调用 layoutIfNeeded ,就可以通知系统立即刷新布局,从而更新Frame。如果要更加“保险”,可以在调用 layoutIfNeeded 前,加上调用 setNeedsUpdateConstraintssetNeedsLayout ,系统刷新布局时,就会先更新约束,然后根据约束重新计算相关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的理解,希望可以一直将这个系列坚持下去~~

原文  http://tutuge.me/2016/08/06/autolayout-example-with-masonry4/
正文到此结束
Loading...