關(guān)于Autolayout的調(diào)試
剛開始使用 Autolayout 遇到下面的警告人容易讓人氣餒,經(jīng)常不知所措而放棄了使用 Autolayout缰犁。
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
,現(xiàn)在介紹下使用 UIViewAlertForUnsatisfiableConstraints
的調(diào)試方法。
在 UIViewAlertForUnsatisfiableConstraints
添加 symbolic breakpoint
:
- 打開斷點(diǎn)導(dǎo)航(cmd+7)
- 點(diǎn)擊左下角的+按鈕
- 選擇Add Symbolic Breakpoint
- 在Symbol添加UIViewAlertForUnsatisfiableConstraints
再次調(diào)試的時候就可以通過 lldb 來調(diào)試了辨绊,然并卵,如果你不知道 lldb 的話匹表。
所以交給你一個小技巧门坷,添加
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 相關(guān)的視圖就是約束有問題的。0x7f9481c9d990就是有問題視圖的首地址袍镀。
當(dāng)然進(jìn)一步的調(diào)試需要 lldb 的命令默蚌。比如,打印視圖對象
(lldb) po 0x7f9481c9d990
<UIView: 0x7f9481c9d990; frame = (0 0; 768 359); autoresize = RM+BM; layer = <CALayer: 0x7fc82d338960>>
改變顏色:
(lldb) expr ((UIView *)0x174197010).backgroundColor = [UIColor redColor]
(UICachedDeviceRGBColor *) $4 = 0x0000000174469cc0
剩下的就是去代碼中找到這個視圖流椒,然后修改其約束了敏簿。
AutoLayout 關(guān)于 update 的幾個方法
UIView 是我們經(jīng)常使用的一個基本控件明也,其中有幾個基本的布局方法需要清楚宣虾。
- layoutSubViews:
當(dāng) View
及其所有子視圖的 frame
發(fā)生改變的時候,會調(diào)用 layoutSubviews
温数,所以在需要更新 frame 來重新定位或更改大小時重載它绣硝。這個方法很開銷很大,因?yàn)樗鼤诿總€子視圖上起作用并且調(diào)用它們相應(yīng)的 layoutSubviews
方法撑刺。注意:最好不要在代碼中手動調(diào)用 layoutSubviews
方法鹉胖。當(dāng) layoutSubviews
完成后,在 view
的所有者 view controller
上够傍,會觸發(fā) viewDidLayoutSubviews
調(diào)用甫菠。因?yàn)?viewDidLayoutSubviews
是 view
布局更新后會被唯一可靠調(diào)用的方法,所以你應(yīng)該把所有依賴于布局或者大小的代碼放在 viewDidLayoutSubviews
中冕屯,而不是放在 viewDidLoad
或者 viewDidAppear
中寂诱。
觸發(fā) layoutSubviews
的時機(jī):
addSubview
方法會觸發(fā)layoutSubviews
。當(dāng)
view
的Frame
發(fā)生變化也會觸發(fā)layoutSubviews
安聘。滾動一個
UIScrollView
會觸發(fā)layoutSubviews
痰洒。旋轉(zhuǎn)屏幕會觸發(fā)父
View
上的layoutSubviews
瓢棒。改變一個
View
大小的時候也會觸發(fā)父View
上的layoutSubviews
。setNeedsLayout
觸發(fā)layoutSubviews
調(diào)用的最省資源的方法就是在你的視圖上調(diào)用setNeedsLaylout
方法丘喻,表示視圖的布局需要重新計算脯宿。告知頁面需要更新,但是不會立刻開始更新視圖泉粉,視圖會在下一個runloop
中更新连霉,調(diào)用setNeedsLaylout
方法視圖被重新繪制并布局之間會有一段任意時間的間隔。layoutIfNeeded
調(diào)用layoutIfNeeded
會觸發(fā)layoutSubviews
嗡靡,告知頁面布局立刻更新窘面,所以一般都會和setNeedsLayout
一起使用。如果希望立刻生成新的frame
需要調(diào)用此方法叽躯,利用這點(diǎn)一般布局動畫可以在更新布局后直接使用這個方法讓動畫生效财边。setNeedsUpdateConstraints
告知需要更新約束,但是不會立刻開始点骑,在下一次runloop
中更新約束酣难,通過標(biāo)記update constraints
來觸發(fā)updateConstraints
。updateConstraintsIfNeeded
告知立刻更新約束黑滴,這個方法與layoutIfNeeded
等價憨募。它會檢查update constraints
標(biāo)記。如果認(rèn)為這些約束需要被更新袁辈,它會立即觸發(fā)updateConstraints
菜谣,而不會等到run loop
的末尾。updateConstraints
系統(tǒng)更新約束晚缩,注意:最好不要在代碼中手動調(diào)用updateConstraints
方法尾膊。通常在updateConstraints
方法中實(shí)現(xiàn)必須要更新的約束,在設(shè)置或者解除約束荞彼、更改約束的優(yōu)先級或者常量值冈敛,或者從視圖層級中移除一個視圖時都會設(shè)置一個內(nèi)部的標(biāo)記update constarints
,這個標(biāo)記會在下一個更新周期中觸發(fā)調(diào)用updateConstrains
鸣皂。
注意:layoutSubViews 在 drawRect 之前調(diào)用抓谴。
AutoLayout 與 Frame
在使用 AutoLayout 的時候可能也會同時也會用到 frame,比如需要用到 layer 的時候寞缝,想讓 layer 的尺寸是由其它視圖尺寸設(shè)定的癌压,而這個視圖又是由約束控制布局的,如果將 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;
// 設(shè)置約束
[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>>
這個時候丐吓,看到為什么設(shè)置了約束浅悉,而打印出來的 frame 是 (0 0; 0 0),是因?yàn)榧s束被設(shè)置之后它并不會立即對 view 作出改變券犁,而是要等到 layout 時术健,才會對視圖的尺寸進(jìn)行修改,而 layout 通常是在視圖已經(jīng)加載到父視圖上面時做出響應(yīng)粘衬。
所以如果在 viewDidLoad 中設(shè)置了約束荞估,那么要等到 viewDidAppear 時 view 的尺寸才會真正改變。
解決辦法:
- (void)testLayout {
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
self.redView = redView;
// 設(shè)置約束
[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>>
1、把獲取 frame 的設(shè)置寫到 layoutSubviews 中或者寫到 viewDidLayoutSubviews 中即可褂删。因?yàn)?layout 約束生效時 view 的 center 或者 bounds 就會被修改飞醉,當(dāng) center 或者 bounds 被修改時layoutSubview 就會被調(diào)用,隨后 viewDidLayoutSubviews 就回被調(diào)用屯阀。這個時候缅帘,設(shè)置約束的視圖 frame 就不再是 (0,0,0,0) 了。
- (void)testLayout {
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
self.redView = redView;
// 設(shè)置約束
[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>>
2、如果將約束和 frame 寫在同一方法中盖袭,寫完約束就設(shè)置 frame失暂,而不是想把 frame 的設(shè)置放到 layoutSubview 中,比如設(shè)置好約束后馬上就想根據(jù)約束的結(jié)果計算高度鳄虱,那么必須在設(shè)置完約束之后手動調(diào)用
setNeedsLayout 和 layoutIfNeeded 方法弟塞,讓視圖立即 layout,更新 frame醇蝴,但是這個時候就可以拿到真實(shí)的 size 并不能拿到真實(shí)的 center 宣肚,不建議這么使用想罕。
- (void)testLayout {
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
self.redView = redView;
// 設(shè)置約束
[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>>
3按价、在 dispatch_after 里面可以拿到真實(shí)的 frame 惭适,或許是因?yàn)樵O(shè)置約束和獲取 frame 不在同一個 runloop 的原因吧。