第四篇来了。
还是3个小例子,仍然是主要部分用Masonry手写代码实现,其它的约束在storyboard里面直接拖拽搭建。
三个例子分别是:
layoutIfNeeded
控制约束的生效时机 前三篇:
Github地址:
https://github.com/zekunyan/AutolayoutExampleWithMasonry
Case 1实现的功能就是,可以用手指拖动一个灰色的Tip控件,在红框containerView内移动,并且保持移动到红框边缘时,Tip控件的内容不会超出红框,保证内容的可见性。
如果不用Autolayout的话,直接计算、设定frame,那么可能的步骤如下:
这样去实现的话,代码将会比较“丑”=。。=,有大量的判断、数值计算,而且容易出错。
而用Autolayout的话, 只需要两步 :
至于保证Tip内容在可见范围内,就交给Autolayout处理吧~
用Autolayout实现的关键,就是 善于利用约束的优先级 。先看看设置的约束示意图:
中间的tipLabel就是被移动的控件
重点其实就在于, 蓝色的约束的优先级比褐色的优先级高,这样,在移动中,系统会优先满足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
。
这一点比较简单,直接将当前触摸的坐标赋值给 leftConstraint
和 rightConstraint
就好:
_leftConstraint.offset = touchPoint.x;
_topConstraint.offset = touchPoint.y;
对比起来,明显是用Autolayout的方法比计算设置frame的方法要“优雅”的多=。=,灵活运用约束的优先级,往往可以减少很多工作量。
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的例子,是一个灰色的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的理解,希望可以一直将这个系列坚持下去~~