剛開始使用 Autolayout 遇到下面的警告人容易讓人氣餒,常常不知所措而放棄了使用 Autolayout。c++
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(...........)
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
複製代碼
正如輸出中所述,Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger
,如今介紹下使用 UIViewAlertForUnsatisfiableConstraints
的調試方法。bash
在 UIViewAlertForUnsatisfiableConstraints
添加 symbolic breakpoint
:ide
再次調試的時候就能夠經過 lldb 來調試了,然並卵,若是你不知道 lldb 的話。oop
因此交給你一個小技巧,添加佈局
po [[UIWindow keyWindow] _autolayoutTrace] // OC項目
expr -l objc++ -O -- [[UIWindow keyWindow] _autolayoutTrace] // Swift項目
複製代碼
這樣就能夠直接看到輸出:動畫
(lldb) po [[UIWindow keyWindow] _autolayoutTrace]
UIWindow:0x7f9481c93360
| •UIView:0x7f9481c9d680
| | *UIView:0x7f9481c9d990- AMBIGUOUS LAYOUT for UIView:0x7f9481c9d990.minX{id: 13}, UIView:0x7f9481c9d990.minY{id: 16}
| | *_UILayoutGuide:0x7f9481c9e160- AMBIGUOUS LAYOUT for _UILayoutGuide:0x7f9481c9e160.minY{id: 17}
| | *_UILayoutGuide:0x7f9481c9ebb0- AMBIGUOUS LAYOUT for _UILayoutGuide:0x7f9481c9ebb0.minY{id: 27}
複製代碼
其中 AMBIGUOUS 相關的視圖就是約束有問題的。0x7f9481c9d990就是有問題視圖的首地址。ui
固然進一步的調試須要 lldb 的命令。好比,打印視圖對象this
(lldb) po 0x7f9481c9d990
<UIView: 0x7f9481c9d990; frame = (0 0; 768 359); autoresize = RM+BM; layer = <CALayer: 0x7fc82d338960>>
複製代碼
改變顏色:spa
(lldb) expr ((UIView *)0x174197010).backgroundColor = [UIColor redColor]
(UICachedDeviceRGBColor *) $4 = 0x0000000174469cc0
複製代碼
剩下的就是去代碼中找到這個視圖,而後修改其約束了。debug
UIView 是咱們常用的一個基本控件,其中有幾個基本的佈局方法須要清楚。
layoutSubViews:
添加子視圖常重寫這個方法,這個方法是用來從新佈局子視圖的,經常使用於對子視圖佈局,或者在其餘方法中調用以達到從新佈局的做用。
setNeedsLayout
告知頁面須要更新,可是不會馬上開始更新,執行後會馬上調用layoutSubviews
。
layoutIfNeeded
告知頁面佈局馬上更新,因此通常都會和setNeedsLayout
一塊兒使用。若是但願馬上生成新的frame
須要調用此方法,利用這點通常佈局動畫能夠在更新佈局後直接使用這個方法讓動畫生效。
setNeedsUpdateConstraints
告知須要更新約束,可是不會馬上開始
updateConstraintsIfNeeded
告知馬上更新約束
updateConstraints
系統更新約束
layoutSubviews
的時機init
方法初始化不會觸發layoutSubviews
,可是是用initWithFrame
進行初始化時,當rect
的值不爲CGRectZero
時,會觸發。addSubview
方法會觸發layoutSubviews
。view
的Frame
會觸發layoutSubviews
,前提是frame
的值設置先後發生了變化。UIScrollView
會觸發layoutSubviews
。Screen
會觸發父UIView
上的layoutSubviews
。UIView
大小的時候也會觸發父UIView
上的layoutSubviews
。注意:layoutSubViews 在 drawRect 以前調用。
在使用 AutoLayout 的時候可能也會同時也會用到 frame,好比須要用到 layer 的時候,想讓 layer 的尺寸是由其它視圖尺寸設定的,而這個視圖又是由約束控制佈局的,若是將 layer 的初始化與 view 的初始化放在一個方法中; 好比:
layer.bounds = CGRectMake(0,0,view.bounds.size.widith * 0.5,50)
複製代碼
那麼極可能拿到 layer 的寬度是0。
好比:
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
self.redView = redView;
// 設置約束
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view.mas_centerX);
make.centerY.equalTo(self.view.mas_centerY);
make.size.mas_equalTo(CGSizeMake(150, 80));
}];
NSLog(@"self.view 的尺寸%@,redView 的尺寸%@",self.view,redView);
2017-06-08 15:32:51.815107+0800 MasonryDemo[42940:1076244] self.view 的尺寸<UIView: 0x7fd8cd408960; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x604000227200>>,redView 的尺寸<UIView: 0x7fd8cd407650; frame = (0 0; 0 0); layer = <CALayer: 0x6040002274a0>>
複製代碼
這個時候,看到爲何設置了約束,而打印出來的 frame 是 (0 0; 0 0),是由於約束被設置以後它並不會當即對 view 做出改變,而是要等到 layout 時,纔會對視圖的尺寸進行修改,而 layout 一般是在視圖已經加載到父視圖上面時作出響應。
因此若是在 viewDidLoad 中設置了約束,那麼要等到 viewDidAppear 時 view 的尺寸纔會真正改變。
解決辦法:
- (void)testLayout {
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
self.redView = redView;
// 設置約束
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view.mas_centerX);
make.centerY.equalTo(self.view.mas_centerY);
make.size.mas_equalTo(CGSizeMake(150, 80));
}];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(@"self.view 的尺寸%@,redView 的尺寸%@",self.view,self.redView);
}
2017-06-08 15:50:41.621147+0800 MasonryDemo[43363:1089098] self.view 的尺寸<UIView: 0x7fe412f0f780; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x604000238b00>>,redView 的尺寸<UIView: 0x7fe412e045b0; frame = (132 328; 150 80); layer = <CALayer: 0x60000003c460>>
複製代碼
一、把獲取 frame 的設置寫到 layoutSubviews 中或者寫到 viewDidLayoutSubviews 中便可。由於 layout 約束生效時 view 的 center 或者 bounds 就會被修改,當 center 或者 bounds 被修改時layoutSubview 就會被調用,隨後 viewDidLayoutSubviews 就回被調用。這個時候,設置約束的視圖 frame 就再也不是 (0,0,0,0) 了。
- (void)testLayout {
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
self.redView = redView;
// 設置約束
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view.mas_centerX);
make.centerY.equalTo(self.view.mas_centerY);
make.size.mas_equalTo(CGSizeMake(150, 80));
}];
[redView setNeedsLayout];
[redView layoutIfNeeded];
NSLog(@"self.view 的尺寸%@,redView 的尺寸%@",self.view,redView);
}
2017-06-08 15:52:32.749105+0800 MasonryDemo[43419:1090641] self.view 的尺寸<UIView: 0x7fe36440b5f0; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x604000422100>>,redView 的尺寸<UIView: 0x7fe364405040; frame = (-75 -40; 150 80); layer = <CALayer: 0x6040004207a0>>
複製代碼
二、若是將約束和 frame 寫在同一方法中,寫完約束就設置 frame,而不是想把 frame 的設置放到 layoutSubview 中,好比設置好約束後立刻就想根據約束的結果計算高度,那麼必須在設置完約束以後手動調用 setNeedsLayout 和 layoutIfNeeded 方法,讓視圖當即 layout,更新 frame,可是這個時候就能夠拿到真實的 size 並不能拿到真實的 center ,不建議這麼使用。
- (void)testLayout {
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
self.redView = redView;
// 設置約束
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view.mas_centerX);
make.centerY.equalTo(self.view.mas_centerY);
make.size.mas_equalTo(CGSizeMake(150, 80));
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"self.view 的尺寸%@,redView 的尺寸%@",self.view,redView);
});
}
2017-06-08 15:55:56.282546+0800 MasonryDemo[43500:1092911] self.view 的尺寸<UIView: 0x7fda85e0d540; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x600000233620>>,redView 的尺寸<UIView: 0x7fda85e0c770; frame = (132 328; 150 80); layer = <CALayer: 0x600000233540>>
複製代碼
三、在 dispatch_after 裏面能夠拿到真實的 frame ,或許是由於設置約束和獲取 frame 不在同一個 runloop 的緣由吧。