用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
定義完Masonry
的bolck
後調用一下[self.view layoutIfNeeded]
,就能立刻獲取到指望值。 猜想應該是VC
調用了某些佈局方法。 而且這些方法在viewWillAppear
和DidAppear
之間也調用了。 如今就來分析爲何會這樣。這個執行順序挺容易理解的。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:收到內存警告
複製代碼
能夠看到和佈局有關的兩個方法確實夾在viewWillAppear
和viewDidAppear
之間,且有可能會調用屢次。函數
// layoutSubviews調用時機
1. 當view被添加到另外一個view上使用
2. 佈局本身子控件時使用
3. 屏幕打橫時
4. 當本身的frame發生變化時,例如手動修改、熱點等。
複製代碼
(實測的時候發現若在viewDidLoad
中,self.view加了(不管多少個)子控件VC會分別調用兩次這些方法,沒添加子控件分別調用一次)。緣由是layoutSubviews的調用時機。被添加到另外一個view時調用一次;若是加了子控件,佈局子控件也會調用一次。oop
首先,Masonry
是創建在autolayout
之上的,最終轉化爲frame
。 一開始讓我驚訝的是,Masonry
約束的bolck
會立刻執行。但frame
不能當即獲取。緣由是RunLoop下一個循環到來纔會刷新UI。佈局
viewDidLoad
定義完Masonry
的block
後,(從上圖能夠看出過了少於0.1秒的時間內)兩佈局方法就調用完成,frame
也被算出來並在畫面上描繪好view
了。 若是定義完後直接調用[self.view layoutIfNeeded]
,不用等到下一個cycle,VC會在該函數內立刻同步調用viewWillLayoutSubviews
和viewDidLayoutSubviews
各一次,這時候frame
就是指望值了。ui
以上弄清楚Masonry
不能當即獲取frame
的緣由了。但都是分析VC
對UIView
的佈局方式。那麼view
中實現內部佈局又是怎麼個程序執行順序呢?spa
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
layoutSubviews
、draw
和updateConstraints
只應該被重載,毫不要在代碼中顯式地調用,系統會在須要的時候自動調用。 舉個例子某個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
updateConstraints
是子控件對父控件的。 layoutSubviews
是父控件對子控件的。會遞歸調用子控件的layoutSubviews
。 display
先渲染父控件,再渲染子控件。update cycle
中,通常不卡的話,1/60s就會更新一遍。view
的以上三個執行標記發生改變,要等到下一次update cycle
後,VC纔會調用佈局方法計算好frame並渲染到屏幕上。updateConstraintsIfNeeded
、layoutIfNeeded
這兩個立刻執行方法是給咱們調用的,告訴系統不用等到下一個update cycle
,VC立刻執行佈局方法。view
的init
後調用needsUpdateConstraints
返回YES
。而暴露的set方法只能標記爲YES
,做用應該是告訴系統下一次cycle
要更新約束。猜想底層佈局好後會有別的set方法置爲NO。- (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);
}];
}
複製代碼
能夠看到init
後把三個標記都置爲YES
。 而後在VC的佈局方式中,viewWillLayoutSubviews
中會調用updateConstraints
,在viewDidLayoutSubviews
會調用layoutSubviews
,draw
。因此說不要顯示調用 怎麼執行 這三個方法。cdn
- (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);
}];
}
複製代碼
updateConstraints
是子到父。
layoutSubviews
和
drawRect
是父到子。
補充 如下情形會調用layoutSubviews
一、init初始化不會觸發layoutSubviews
可是是用initWithFrame 進行初始化時,當rect的值不爲CGRectZero時,也會觸發
二、addSubview會觸發layoutSubviews
三、設置view的Frame會觸發layoutSubviews,固然前提是frame的值設置先後發生了變化
四、滾動一個UIScrollView會觸發layoutSubviews
五、旋轉Screen會觸發父UIView上的layoutSubviews事件
六、改變一個UIView大小的時候也會觸發父UIView上的layoutSubviews事件
七、直接調用setLayoutSubviews。
八、直接調用setNeedsLayout。
複製代碼