VC的佈局時機、所用方法以及UIView內部佈局執行順序

前言

據說首圖能吸引人點進來

Masonry時,剛設置完佈局後想使用frame乾點壞事,發現並非指望的值bash

- (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的生命週期

這個執行順序挺容易理解的。app

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之間,且有可能會調用屢次。函數

// layoutSubviews調用時機
1. 當view被添加到另外一個view上使用
2. 佈局本身子控件時使用
3. 屏幕打橫時
4. 當本身的frame發生變化時,例如手動修改、熱點等。
複製代碼

(實測的時候發現若在viewDidLoad中,self.view加了(不管多少個)子控件VC會分別調用兩次這些方法,沒添加子控件分別調用一次)。緣由是layoutSubviews的調用時機。被添加到另外一個view時調用一次;若是加了子控件,佈局子控件也會調用一次。oop


接下來分析[self.view layoutIfNeeded]爲何能實現當即刷新frame

首先,Masonry是創建在autolayout之上的,最終轉化frame。 一開始讓我驚訝的是,Masonry約束的bolck會立刻執行。但frame不能當即獲取。緣由是RunLoop下一個循環到來纔會刷新UI。佈局

frame生成的過程

結論:

viewDidLoad定義完Masonryblock後,(從上圖能夠看出過了少於0.1秒的時間內)兩佈局方法就調用完成frame也被算出來並在畫面上描繪好view了。 若是定義完後直接調用[self.view layoutIfNeeded],不用等到下一個cycle,VC會在該函數內立刻同步調用viewWillLayoutSubviewsviewDidLayoutSubviews各一次,這時候frame就是指望值了。ui


以上弄清楚Masonry不能當即獲取frame緣由了。但都是分析VCUIView佈局方式。那麼view中實現內部佈局又是怎麼個程序執行順序呢?spa


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畫自定義的內容,就要實現該方法,不然避免覆蓋該方法。
複製代碼

分享一下我對這些方法的理解,應該對理解後面過程有幫助。若是有錯誤的地方,歡迎指出來。.net

  • 總體分紅了 怎麼執行須要執行的標記立刻執行佈局
  • 怎麼執行的三類方法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調用佈局方法時天然會跑這個方法。code

  • 不少狀況下系統都會把view的須要執行標記置爲YES。
  • updateConstraints是子控件對父控件的。 layoutSubviews是父控件對子控件的。會遞歸調用子控件的layoutSubviewsdisplay先渲染父控件,再渲染子控件。
  • 佈局運行在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。因此說不要顯示調用 怎麼執行 這三個方法。cdn

  • 情形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

一、init初始化不會觸發layoutSubviews 
可是是用initWithFrame 進行初始化時,當rect的值不爲CGRectZero時,也會觸發

二、addSubview會觸發layoutSubviews

三、設置view的Frame會觸發layoutSubviews,固然前提是frame的值設置先後發生了變化

四、滾動一個UIScrollView會觸發layoutSubviews

五、旋轉Screen會觸發父UIView上的layoutSubviews事件

六、改變一個UIView大小的時候也會觸發父UIView上的layoutSubviews事件

七、直接調用setLayoutSubviews。

八、直接調用setNeedsLayout。
複製代碼

參考

相關文章
相關標籤/搜索