IOS布局浅析

理解Update Cycle摘自《[译] 揭秘 iOS 布局》

UPdate Cycle是当应用完成了你所有的事件处理代码之后回到主RunLoop时的时间点。正是在这个时间点上开始更新布局、显示和设置约束。如果你在处理事件的代码中请求修改了一个view,那么系统会把这个view标记为重画。接下来的Update cycle中,系统就会执行view的更改。用户交互和布局更新的延迟几乎不会被用户察觉到,IOS一般以60fps的速度展示动画,也就是一个周期大概是1/60s。这个更新过程很快,用户也很难察觉到。所以用户察觉不到UI中更新的延迟,但是由于处理事件和对应view重画中间存在着间隔,RunLoop中某时刻的view更新可能不是你想要的那样。如果你的代码中的某些设计依赖于当下的view内容或者布局,那么就有在过时view信息上操作的风险。

意思就是当我们代码更新view的布局的时候,界面上的UI并不会马上更新,而是会在下一次Update Cycle的时候更新。

layoutSubviews()

这个方法的作用其实就是通知当前的view你的布局发生变化了,你要重新布局你的subviews,该方法不要手动调用,系统会在需要调用的时候自动调用。如下几种情况会触发 layoutSubviews()

  • 修改view的大小
  • 新增subview
  • 用户在UIScrollView上滚动
  • 旋转屏幕
  • 更新视图的约束

setNeedsLayout()

顾名思义需要设置布局,就是告诉系统这个视图需要更新布局,这个方法会立即返回,但是view会在下一次Update cycle中更新,调用视图们的layoutSubviews()


layoutIfNeeded()

layoutIfNeeded()不一定会触发view的layoutSubviews。系统会检测layoutSubviews的触发条件,如果符合条件,那么会立即触发layoutSubviews(),不会像setNeedsLayout()等待下一次Update cycle。如果不符合layoutSubviews的触发条件则不会触发。
这里可以解释自动布局在开始执行动画的时候为什么必须执行setNeedsLayout(),如果不添加setNeedsLayout()不会执行动画。
如下代码的结果是2s之后无动画。

    self.viewOne = [[TestViewOne alloc] initWithFrame:CGRectZero];
    self.viewOne.backgroundColor = UIColor.redColor;
    [self.view addSubview:self.viewOne];
    [self.viewOne mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.mas_equalTo(self.view);
        make.width.mas_equalTo(100);
        make.height.mas_equalTo(100);
    }];
      
    [UIView animateWithDuration:2 animations:^{
            [self.viewOne mas_remakeConstraints:^(MASConstraintMaker *make) {
                make.center.mas_equalTo(self.view);
                make.width.mas_equalTo(50);
                make.height.mas_equalTo(50);
            }];
           NSLog(@"动画打印viewOne的frame%@",NSStringFromCGRect(self.viewOne.frame));
        }];
//打印结果
2019-03-13 17:44:27.115060+0800 layoutStudy[16692:523774] 动画打印viewOne的frame{{182, 423}, {100, 100}}

如果添加进去layoutIfNeeded()

    self.viewOne = [[TestViewOne alloc] initWithFrame:CGRectZero];
    self.viewOne.backgroundColor = UIColor.redColor;
    [self.view addSubview:self.viewOne];
    [self.viewOne mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.mas_equalTo(self.view);
        make.width.mas_equalTo(100);
        make.height.mas_equalTo(100);
    }];
      
    [UIView animateWithDuration:2 animations:^{
            [self.viewOne mas_remakeConstraints:^(MASConstraintMaker *make) {
                make.center.mas_equalTo(self.view);
                make.width.mas_equalTo(50);
                make.height.mas_equalTo(50);
            }];
          [self.viewOne layoutIfNeeded];
           NSLog(@"动画打印viewOne的frame%@",NSStringFromCGRect(self.viewOne.frame));
     }];
//打印结果
2019-03-13 17:44:27.115060+0800 layoutStudy[16692:523774] 动画打印viewOne的frame{{182, 423}, {50, 50}}

其实自动布局也是通过一套引擎转换成frame,但是这个过程并不是马上进行的会在下一次Update Cycle执行,调用layoutIfNeeded会强制计算frame绘制完毕会调用layoutSubviews,并且被animation的block截获。可以明显的看出代码1中self.viewOne的frame没有变,但是代码2的frame被提前绘制出来了,所以代码2有动画,代码1没有动画。


总结

那么我们的布局代码应该怎么写呢,我觉得不改变的静态布局直接在init的时候写就可以,把与自身视图尺寸有依赖的约束写到里面,由于layoutSubviews可能会多次调用。不要在其中写比较耗时的操作和初始化对象。

你可能感兴趣的:(IOS布局浅析)