大家都知道既们,系統(tǒng)要展示一個(gè) UIView
居扒,必須要知道它的位置和大小。
在不使用 AutoLayout
的時(shí)候暂题,我們通過設(shè)置 frame
屬性來告訴系統(tǒng)這個(gè) UIView
的位置和大小勋磕。
如果使用了 AutoLayout
,則需要我們?yōu)?UIView
添加一些約束來讓系統(tǒng)自己計(jì)算 UIView
的位置和大小敢靡。
為什么會(huì)產(chǎn)生約束沖突
想要解決約束沖突挂滓,首先我們要先知道為什么會(huì)產(chǎn)生約束沖突。
場景一
假設(shè)我有兩個(gè) UIView
啸胧,分別為 view1
和 view2
赶站, view1
的位置和大小確定, view2
的頂部距離 view1
的底部距離固定纺念, view2
的左邊距贝椿、大小和 view1
相等。如以下代碼:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(50);
make.left.mas_equalTo(20);
make.width.mas_equalTo(200);
make.height.mas_equalTo(50);
}];
[view2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(view1.mas_bottom).offset(10);
make.left.height.width.equalTo(view1);
}];
這時(shí)陷谱,運(yùn)行后的結(jié)果如下圖:
這是一個(gè)最簡單的使用 Masonry
布局例子烙博。
場景二
如果我要給 view2
增加一個(gè)約束:寬度不能大于100瑟蜈,也就是如果 view1
的寬度小于100時(shí),我要讓 view2
的寬度等于 view1
的寬度渣窜,而如果 view1
的寬度大于100時(shí)铺根, view2
的寬度等于100。如果直接這樣設(shè)置 view2
的約束:
[view2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(view1.mas_bottom).offset(10);
make.left.height.width.equalTo(view1);
make.width.mas_lessThanOrEqualTo(100);
}];
那么系統(tǒng)會(huì)報(bào)警告乔宿,提示約束沖突:
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.
(
"<MASLayoutConstraint:0x6000000b5720 UIView:0x7fb8fb008870.width == 200>",
"<MASLayoutConstraint:0x6000000b5d20 UIView:0x7fb8fb00c150.width == UIView:0x7fb8fb008870.width>",
"<MASLayoutConstraint:0x6000000b5de0 UIView:0x7fb8fb00c150.width <= 100>"
)
Will attempt to recover by breaking constraint
<MASLayoutConstraint:0x6000000b5de0 UIView:0x7fb8fb00c150.width <= 100>
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.
那么為什么會(huì)產(chǎn)生這樣的沖突呢位迂?原因很簡單,view1
的寬度等于200详瑞,而 view2
的寬度等于 view1
的寬度掂林,所以它的寬度也應(yīng)為200,但 view2
的寬度不能大于100坝橡,所以系統(tǒng)不知道 view2
的寬度到底是多少泻帮。
解決約束沖突
通過上面這個(gè)例子,就引出了我們解決約束沖突的方法:設(shè)置優(yōu)先級(jí)计寇。
當(dāng)兩個(gè)約束產(chǎn)生沖突時(shí)锣杂,iOS會(huì)棄用優(yōu)先級(jí)低的約束,而選擇優(yōu)先級(jí)較高的約束饲常,達(dá)到正確布局的效果蹲堂。
所以,針對(duì)上面那種情況贝淤,我們只需將 view2
的約束寫為以下這種就行了:
[view2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(view1.mas_bottom).offset(10);
make.left.height.equalTo(view1);
make.width.equalTo(view1).priorityLow();
make.width.mas_lessThanOrEqualTo(100);
}];
效果如下圖:
延伸--IntrinsicContentSize
大家可能有時(shí)會(huì)發(fā)現(xiàn)柒竞,我們?cè)谠O(shè)置 UILabel
的約束時(shí),可以不設(shè)置它的大小約束播聪,這樣它就會(huì)根據(jù)自己的內(nèi)容(文字)來確定它的大小朽基。如一下場景:
場景三
兩個(gè) UILabel
,分別為 label1
和 label2
离陶,其中 label1
左邊距和上邊距確定稼虎, label2
的上邊距和 label1
相同,左邊距離 label1
的右邊距離確定招刨,如以下約束:
[label1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(200);
make.left.mas_equalTo(20);
}];
[label2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(label1.mas_right).offset(10);
make.top.equalTo(label1);
}];
我們并沒有設(shè)置兩個(gè) UILabel
的大小約束霎俩,運(yùn)行效果如下:
可以發(fā)現(xiàn),兩個(gè) UILabel
的寬度和高度都根據(jù)它們的內(nèi)容來設(shè)置了沉眶。而這是為什么呢打却?原來 UIView
具有一個(gè)屬性: CGSize intrinsicContentSize
,含義如下:
Intrinsic Content Size:固有大小谎倔。
顧名思義柳击,在AutoLayout中,如果沒有指定大小片习,那么 UIView 的大小就按照 intrinsicContentSize 來設(shè)置捌肴。
所以蹬叭,雖然我們沒有設(shè)置這兩個(gè) UILabel
的大小約束,它們還是根據(jù)自己的內(nèi)容設(shè)置好了大小状知,展示出來秽五。
CGSize intrinsicContentSize
這個(gè)屬性是 readOnly
的,所以如果我們要修改它试幽,需要定義一個(gè)子類筝蚕,重寫這個(gè)屬性的 getter
方法卦碾。
值得一提的是如果我們不想使用這個(gè)屬性铺坞,則可以在重寫的 getter
方法中直接返回 return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
就行了。
場景四
現(xiàn)在洲胖,我要增加一個(gè) label2
的約束:讓它的右邊距距離父視圖的最右邊20px济榨,如下:
[label2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(label1.mas_right).offset(10);
make.top.equalTo(label1);
make.right.equalTo(self.view.mas_right).offset(-20);
}];
此時(shí),就產(chǎn)生了一個(gè)問題:正常情況下绿映, label2
的最右邊是不可能只距離父視圖20px的擒滑,這就使得肯定有一個(gè) UILabel
不能使用它的 intrinsicContentSize
了,那么應(yīng)該修改哪個(gè) UILabel
的寬度來讓 label2
滿足這個(gè)約束呢叉弦?
解決方案還是設(shè)置優(yōu)先級(jí)丐一,但這次我們需要設(shè)置的是兩種約束的優(yōu)先級(jí): contentHugging
和 contentCompressionResistance
。
contentHugging(不想變大約束):如果組件的此屬性優(yōu)先級(jí)比另一個(gè)組件此屬性優(yōu)先級(jí)高的話淹冰,那么這個(gè)組件就保持不變库车,另一個(gè)可以在需要拉伸的時(shí)候拉伸。
contentCompressionResistance (不想變小約束):如果組件的此屬性優(yōu)先級(jí)比另一個(gè)組件此屬性優(yōu)先級(jí)高的話樱拴,那么這個(gè)組件就保持不變柠衍,另一個(gè)可以在需要壓縮的時(shí)候壓縮。
所以晶乔,如果我們需要 label1
不使用 intrinsicContentSize
珍坊, label2
使用 intrinsicContentSize
,則可以將 label1
的contentHugging
優(yōu)先級(jí)設(shè)為低優(yōu)先級(jí)正罢,反之阵漏,如果我們需要 label1
使用 intrinsicContentSize
,則可以將其 contentHugging
的優(yōu)先級(jí)設(shè)為高優(yōu)先級(jí)翻具。
那么怎么設(shè)置它們這兩個(gè)約束的優(yōu)先級(jí)呢履怯?
UIView
提供了兩個(gè)方法給我們?cè)O(shè)置它的這兩個(gè)約束的優(yōu)先級(jí):
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis;
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis;
其中,第一個(gè)參數(shù)為優(yōu)先級(jí)呛占,具體有以下幾種:
static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000; // A required constraint. Do not exceed this.
static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750; // This is the priority level with which a button resists compressing its content.
static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250; // This is the priority level at which a button hugs its contents horizontally.
static const UILayoutPriority UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) = 50;
當(dāng)然虑乖,你也可以自己通過數(shù)字來設(shè)置它們的優(yōu)先級(jí)。
第二個(gè)參數(shù)代表你要設(shè)置約束優(yōu)先級(jí)的方向晾虑,分為橫向和縱向:
typedef NS_ENUM(NSInteger, UILayoutConstraintAxis) {
UILayoutConstraintAxisHorizontal = 0,
UILayoutConstraintAxisVertical = 1
};
以上場景疹味,我們可以通過以下代碼來讓 label1
使用 intrinsicContentSize
仅叫, label2
不使用 intrinsicContentSize
:
[label1 setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
效果如下圖: