现在iOS页面布局用的最多的就是Frame和Autolayout,在Autolayout通过Masonry封装在实际使用中也十分方便。实际上,Autolayout的约束最后都是系统最终转化成frame来进行布局的,对与一个View来说,最终确定其中心点位置和View的宽高。当Autolayout和Frame设置上产生冲突的时候,则会以Autolayout的设置为准。这篇主要讨论布局中常用的几个方法和autolayout遇到动画的情形。
 
跟布局相关的方法
| 12
 3
 
 | - (void)setNeedsLayout;- (void)layoutIfNeeded;
 - (void)layoutSubviews;
 
 | 
setNeedsLayout方法标记当前view是需要重新布局的,在下一次runloop中,进行重新布局。如果说想在当前runloop中立刻更新布局,则通过调用layoutIfNeeded方法可以实现,此时系统会调用layoutSubviews。在layoutSubviews方法中,可以自己定义新的view或者改变子view的布局。
Autolayout相关的方法
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | //view的方法- (void)updateConstraintsIfNeeded;
 //重写view中的方法
 - (void)updateConstraints
 - (BOOL)needsUpdateConstraints
 - (void)setNeedsUpdateConstraints
 
 //重写viewController中的方法
 - (void)updateViewConstraints
 
 | 
setNeedsUpdateConstraints只是标记当前view的约束需要在下一次runloop中更新,updateConstraintsIfNeeded如果过满足更新条件会立刻调用updateConstraints来更新约束,updateConstraints是子view需要重写的方法,来更新View的约束,最后需要调用[super updateConstraints],否则会崩。而updateViewConstraints是定义在viewController中的,方便去更新viewController对应view的约束。
具体可以通过调用view的setNeedsUpdateConstraints来最终调用到viewController的updateViewConstraints方法来,如果没有这个方法,那么每次都要定义一个子view去重写updateConstraints方法会比较繁琐。updateConstraints和updateViewConstrains方法可以把约束的代码和和业务逻辑分开,另外性能也更好。
为什么会有setxxxxx和xxxifNeeded方法
setxxxxx方法可能是为了性能,没有在代码更新布局或者约束之后立刻执行,而是在下一次runloop中执行。
xxxxIfNeeded方法则是为了在必要的时候,立刻更新约束或者布局,举个例子,有些时候同时使用动画和autolayout。
Autolayout和动画
现在实现一个view滑动的动画:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | @interface MyView : UIView
 -(void)layoutSubviews;
 @end
 @implementation MyView
 
 -(void)layoutSubviews
 {
 [super layoutSubviews];
 }
 @end
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 
 | - (void)viewDidLoad {
 [super viewDidLoad];
 
 
 _container = [[MyView alloc] initWithFrame:self.view.frame];
 
 [self.view addSubview:_container];
 
 
 _redView = [[MyView alloc] initWithFrame:CGRectMake(100, 100, 100, 50)];
 
 _redView.backgroundColor = [UIColor redColor];
 
 [_container addSubview:_redView];
 
 [_redView mas_makeConstraints:^(MASConstraintMaker *make) {
 
 make.centerX.equalTo(self.view);
 
 make.centerY.equalTo(self.view);
 
 make.width.height.equalTo(@100);
 
 }];
 
 
 
 UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
 
 btn.titleLabel.text = @"点击动画";
 
 btn.backgroundColor = [UIColor greenColor];
 
 [_container addSubview:btn];
 
 [btn mas_makeConstraints:^(MASConstraintMaker *make) {
 
 make.centerX.equalTo(self.view);
 
 make.centerY.equalTo(self.view).offset(100);
 
 make.width.equalTo(@100);
 
 make.height.equalTo(@50);
 
 }];
 
 [btn addTarget:self action:@selector(onButtonClick:) forControlEvents:
 UIControlEventTouchUpInside];
 
 }
 
 | 
使用frame
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | - (void)onButtonClick:(id)sender
 {
 [UIView animateWithDuration:1 animations:^{
 
 self.redView.frame = CGRectMake(0, self.redView.frame.origin.y, self.
 redView.frame.size.width, self.redView.frame.size.height);
 
 }];
 }
 
 | 
和预期结果一致,红色的view滑动到屏幕的最左侧。
使用autolayout
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | - (void)onButtonClick:(id)sender
 {
 [UIView animateWithDuration:1 animations:^{
 [self.redView mas_remakeConstraints:^(MASConstraintMaker *make) {
 
 make.width.height.equalTo(@100);
 
 make.left.centerY.equalTo(self.view);
 
 }];
 }
 
 | 
和预期的结果不一致,红色的view突然一下移动到屏幕的最左侧,上面这种做法是有问题的,现在在动画的block中添加一行日志的代码。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | - (void)onButtonClick:(id)sender{
 [UIView animateWithDuration:1 animations:^{
 [self.redView mas_remakeConstraints:^(MASConstraintMaker *make) {
 make.width.height.equalTo(@100);
 make.left.centerY.equalTo(self.view);
 }];
 NSLog(@"redView x = %@", @(self.redView.frame.origin.x));
 }];
 }
 
 | 
输出日志:
| 1
 | 2016-09-07 17:44:03.069 2323[99216:10267632] redView x = 137.5(原来的位置)
 | 
说明在重新更新红色View的约束之后,系统并没有立刻转化成对应的frame值,还是原来的位置,但是为什么view会移动到屏幕最右侧而不是静止呢。
因为系统在计算动画插值的过程中,发现红色view的前后的位置是一样的,最后的结果就是在原地静止,猜测有可能系统为了优化直接取消了动画。这是在当前runloop发生的逻辑,在下一次runloop过程中,上一次给红色view设置的约束就生效了,系统会将约束更新到frame的表现上,所以红色view直接跑到了屏幕最左侧。
现在的问题是需要在系统计算动画插值的时候,将视图的frame即时的更新,所以调用红色view的layoutIfNeeded方法就好了。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | - (void)onButtonClick:(id)sender{
 [UIView animateWithDuration:1 animations:^{
 [self.redView mas_remakeConstraints:^(MASConstraintMaker *make) {
 
 make.width.height.equalTo(@100);
 
 make.left.centerY.equalTo(self.view);
 
 }];
 
 NSLog(@"redView x = %@", @(self.redView.frame.origin.x));
 
 [self.container layoutIfNeeded];
 
 NSLog(@"redView x = %@", @(self.redView.frame.origin.x));
 
 }];
 }
 
 | 
日志输出为:
| 12
 3
 
 | 2016-09-07 17:50:33.297 2323[99250:10270639] redView x = 137.5
 2016-09-07 17:50:33.299 2323[99250:10270639] redView x = 0
 
 | 
通过对比可以看到,在调用parentView的layoutIfNeeded之后,其frame得到更新了,所以最后动画如预期一样出现了。这里必须是调用parentView的layoutIfNeeded方法,调用红色视图的layoutIfNeeded方法是不会更新它自己的frame的。