美文网首页好东西小知识点
VC的布局时机、所用方法以及UIView内部布局执行顺序

VC的布局时机、所用方法以及UIView内部布局执行顺序

作者: Hsusue | 来源:发表于2018-07-27 16:54 被阅读96次

    前言

    听说首图能吸引人点进来

    Masonry时,刚设置完布局后想使用frame干点坏事,发现并不是期望的值

    - (void)viewDidLoad {
        self.btn = [[UIButton alloc] init];
        [self.view addSubview:self.btn];
        [self.btn mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.equalTo(self.view.mas_centerX);
            make.centerY.equalTo(self.view.mas_centerY);
            make.width.equalTo(@100);
            make.height.equalTo(@50);
        }];
        NSLog(@"%@",self.btn);// 输出frame = (0 0; 0 0) 
    //  [self.view layoutIfNeeded];
    }
    

    然后试了在viewWillAppear首次出现界面还是没获取到期望值,
    viewDidAppear才获取到期望值。
    解决方法:在viewDidLoad定义完Masonrybolck后调用一下[self.view layoutIfNeeded],就能马上获取到期望值
    猜测应该是VC调用了某些布局方法
    并且这些方法在viewWillAppearDidAppear之间也调用了。
    现在就来分析为什么会这样。


    controller对view的布局时机和所用方法


    VC的生命周期

    这个执行顺序挺容易理解的。

    alloc:创建对象,分配空间
    init (xib和非xib用initWithNibName、stroyBoard用initWithCoder) :初始化对象,初始化数据
    awakeFromNib:(若控制器有关联xib才调用这方法)
    loadView:优先从nib载入控制器视图 ,其次代码
    viewDidLoad:载入完成,可以进行自定义数据以及动态创建其他控件。
    viewWillAppear:视图将出现在屏幕之前,马上这个视图就会被展现在屏幕上了
    viewWillLayoutSubviews:控制器的view将要布局子控件
    viewDidLayoutSubviews:控制器的view布局子控件完成
    //这期间系统可能会多次调用viewWillLayoutSubviews 、    viewDidLayoutSubviews 俩个方法
    viewDidAppear:视图已在屏幕上渲染完成
    viewWillDisappear:视图将被从屏幕上移除之前执行
    viewDidDisappear:视图已经被从屏幕上移除,用户看不到这个视图了
    dealloc:视图被销毁,此处需要对你在init和viewDidLoad中创建的对象进行释放
    didReceiveMemoryWarning:收到内存警告
    
    调用顺序

    可以看到和布局有关的两个方法确实夹在viewWillAppearviewDidAppear之间,且有可能会调用多次,但只有在首次appear才会自动调用。
    (实测的时候发现若在viewDidLoad中,self.view加了(无论多少个)子控件VC会分别调用两次这些方法,没添加子控件分别调用一次)


    接下来分析[self.view layoutIfNeeded]为什么能实现立即刷新frame

    首先,Masonry是建立在autolayout之上的,最终转化frame
    一开始让我惊讶的是,Masonry约束的bolck会马上执行。但frame不能立即获取。只能说该约束要在别的方法才能frame

    frame生成的过程

    结论:

    viewDidLoad定义完Masonryblock后,(从上图可以看出过了少于0.1秒的时间内)两布局方法就调用完成frame也被算出来并在画面上描绘好view了。
    如果定义完后直接调用[self.view layoutIfNeeded]后,VC会在该函数内马上同步调用viewWillLayoutSubviewsviewDidLayoutSubviews各一次,这时候frame就是期望值了。


    以上弄清楚Masonry不能立即获取frame原因了。但都是分析VCUIView布局方式。那么view中实现内部布局又是怎么个程序执行顺序呢?


    UIView内部布局执行顺序

    布局相关方法

    • 可以分为三块
      updateConstraints <--> layout --> display
      前两个与布局有关,第三个与渲染有关。
    // 三块的主要方法
    #pragma mark - updateConstraints
    //看上去好像set和get方法,但是set方法并无参数,调用就会标记为YES。
    //init后调用get方法发现是YES。
    setNeedsUpdateConstraints:标记需要updateConstraints。
    needsUpdateConstraints:返回是否需要updateConstraints。
    
    
    updateConstraintsIfNeeded:若需要,马上updateConstraints。
    updateConstraints:更新约束,自定义view应该重写此方法在其中建立constraints. 注意:要在最后调用[super updateConstraints]
    
    #pragma mark - layout
    layoutIfNeeded:使用此方法强制立即进行layout,从当前view开始,此方法会遍历整个view层次(包括superviews)请求layout。因此,调用此方法会强制整个view层次布局。
    setNeedsLayout:此方法会将view当前的layout设置为无效的,并在下一个upadte cycle里去触发layout更新。
    layoutSubviews:如果你需要更精确控制子view,而不是使用限制或autoresizing行为,就需要实现该方法。
    
    #pragma mark - display
    setNeedsDisplay:标记整个视图的边界矩形需要重绘.
    drawRect:如果你的View画自定义的内容,就要实现该方法,否则避免覆盖该方法。
    

    分享一下我对这些方法的理解,应该对理解后面过程有帮助。如果有错误的地方,欢迎指出来。

    • 整体分成了 怎么执行需要执行的标记马上执行布局
    • 怎么执行的三类方法layoutSubviewsdrawupdateConstraints只应该被重载,绝不要在代码中显式地调用,系统会在需要的时候自动调用
      举个例子
    某个view.m
      - (void)updateConstraints {
        [self.sourceCollectionView mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.edges.equalTo(self);
        }];
        //super必须写在最后
        [super updateConstraints];
    }
    
    - (void)setModelArray:(NSArray *)modelArray{
        CGRect newFrame = self.frame;
        newFrame.size.height = modelArray.count * 35;
        self.frame = newFrame;
    
        [self updateConstraintsIfNeeded];
    }
    

    updateConstraints里写好了view内部某个Collectionview的布局。当传入模型后,view的高度改变,调用updateConstraintsIfNeeded或者setNeedsUpdateConstraints,而不要显示调用updateConstraints,在VC调用布局方法时自然会跑这个方法。

    • 很多情况下系统都会把view的需要执行标记置为YES。
    • updateConstraints是子控件对父控件的。
      layoutSubviews是父控件对子控件的。会递归调用子控件的layoutSubviews
      display先渲染父控件,再渲染子控件。
    • 布局运行在update cycle中,一般不卡的话,1/60s就会更新一遍。
    • view的以上三个执行标记发生改变,要等到下一次update cycle后,VC才会调用布局方法计算好frame并渲染到屏幕上。
    • updateConstraintsIfNeededlayoutIfNeeded这两个马上执行方法是给我们调用的,告诉系统不用等到下一个update cycle,VC马上执行布局方法。
    • viewinit后调用needsUpdateConstraints返回YES。而暴露的set方法只能标记为YES,作用应该是告诉系统下一次cycle要更新约束。猜测底层布局好后会有别的set方法置为NO。

    分析执行顺序

    • 情形1
      创建HSUTestView
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        self.testView = [[HSUTestView alloc] init];
        self.testView.backgroundColor = [UIColor blueColor];
        [self.view addSubview:self.testView];
        [self.testView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.equalTo(self.view.mas_centerX);
            make.centerY.equalTo(self.view.mas_centerY);
            make.width.equalTo(@100);
            make.height.equalTo(@50);
        }];
    }
    
    UIView内部执行顺序

    可以看到init后把三个标记都置为YES
    然后在VC的布局方式中,viewWillLayoutSubviews中会调用updateConstraints,在viewDidLayoutSubviews会调用layoutSubviewsdraw。所以说不要显示调用 怎么执行 这三个方法。

    • 情形2
      创建HSUContentView 然后add HSUTestView
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        HSUContentView *contentView = [[HSUContentView alloc] init];
        [self.view addSubview:contentView];
        [contentView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.equalTo(self.view.mas_centerX);
            make.centerY.equalTo(self.view.mas_centerY);
            make.width.equalTo(@100);
            make.height.equalTo(@50);
        }];
        self.testView = [[HSUTestView alloc] init];
        self.testView.backgroundColor = [UIColor blueColor];
        [contentView addSubview:self.testView];
        [self.testView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.equalTo(contentView);
        }];
    }
    
    view加view的执行顺序
    可以看到updateConstraints是子到父。layoutSubviewsdrawRect是父到子。

    最后一张图总结UIView内部布局执行顺序与VC的交互

    UIView内部布局执行顺序与VC的交互

    补充 以下情形会调用layoutSubviews

    1、init初始化不会触发layoutSubviews 
    但是是用initWithFrame 进行初始化时,当rect的值不为CGRectZero时,也会触发
    
    2、addSubview会触发layoutSubviews
    
    3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化
    
    4、滚动一个UIScrollView会触发layoutSubviews
    
    5、旋转Screen会触发父UIView上的layoutSubviews事件
    
    6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件
    
    7、直接调用setLayoutSubviews。
    
    8、直接调用setNeedsLayout。
    

    参考

    相关文章

      网友评论

        本文标题:VC的布局时机、所用方法以及UIView内部布局执行顺序

        本文链接:https://www.haomeiwen.com/subject/xicimftx.html