大家都知道,系統(tǒng)要展示一個 UIView
,必須要知道它的位置和大小。
在不使用 AutoLayout
的時候,我們通過設(shè)置 frame
屬性來告訴系統(tǒng)這個 UIView
的位置和大小。
如果使用了 AutoLayout
,則需要我們?yōu)?UIView
添加一些約束來讓系統(tǒng)自己計(jì)算 UIView
的位置和大小。
為什么會產(chǎn)生約束沖突
想要解決約束沖突,首先我們要先知道為什么會產(chǎn)生約束沖突。
場景一
假設(shè)我有兩個 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);
}];
這時,運(yùn)行后的結(jié)果如下圖:
這是一個最簡單的使用 Masonry
布局例子。
場景二
如果我要給 view2
增加一個約束:寬度不能大于100,也就是如果 view1
的寬度小于100時,我要讓 view2
的寬度等于 view1
的寬度,而如果 view1
的寬度大于100時, 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)會報(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.
那么為什么會產(chǎn)生這樣的沖突呢?原因很簡單,view1
的寬度等于200,而 view2
的寬度等于 view1
的寬度,所以它的寬度也應(yīng)為200,但 view2
的寬度不能大于100,所以系統(tǒng)不知道 view2
的寬度到底是多少。
解決約束沖突
通過上面這個例子,就引出了我們解決約束沖突的方法:設(shè)置優(yōu)先級。
當(dāng)兩個約束產(chǎn)生沖突時,iOS會棄用優(yōu)先級低的約束,而選擇優(yōu)先級較高的約束,達(dá)到正確布局的效果。
所以,針對上面那種情況,我們只需將 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
大家可能有時會發(fā)現(xiàn),我們在設(shè)置 UILabel
的約束時,可以不設(shè)置它的大小約束,這樣它就會根據(jù)自己的內(nèi)容(文字)來確定它的大小。如一下場景:
場景三
兩個 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è)置兩個 UILabel
的大小約束,運(yùn)行效果如下:
可以發(fā)現(xiàn),兩個 UILabel
的寬度和高度都根據(jù)它們的內(nèi)容來設(shè)置了。而這是為什么呢?原來 UIView
具有一個屬性: CGSize intrinsicContentSize
,含義如下:
Intrinsic Content Size:固有大小。
顧名思義,在AutoLayout中,如果沒有指定大小,那么 UIView 的大小就按照 intrinsicContentSize 來設(shè)置。
所以,雖然我們沒有設(shè)置這兩個 UILabel
的大小約束,它們還是根據(jù)自己的內(nèi)容設(shè)置好了大小,展示出來。
CGSize intrinsicContentSize
這個屬性是 readOnly
的,所以如果我們要修改它,需要定義一個子類,重寫這個屬性的 getter
方法。
值得一提的是如果我們不想使用這個屬性,則可以在重寫的 getter
方法中直接返回 return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
就行了。
場景四
現(xiàn)在,我要增加一個 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);
}];
此時,就產(chǎn)生了一個問題:正常情況下, label2
的最右邊是不可能只距離父視圖20px的,這就使得肯定有一個 UILabel
不能使用它的 intrinsicContentSize
了,那么應(yīng)該修改哪個 UILabel
的寬度來讓 label2
滿足這個約束呢?
解決方案還是設(shè)置優(yōu)先級,但這次我們需要設(shè)置的是兩種約束的優(yōu)先級: contentHugging
和 contentCompressionResistance
。
contentHugging(不想變大約束):如果組件的此屬性優(yōu)先級比另一個組件此屬性優(yōu)先級高的話,那么這個組件就保持不變,另一個可以在需要拉伸的時候拉伸。
contentCompressionResistance (不想變小約束):如果組件的此屬性優(yōu)先級比另一個組件此屬性優(yōu)先級高的話,那么這個組件就保持不變,另一個可以在需要壓縮的時候壓縮。
所以,如果我們需要 label1
不使用 intrinsicContentSize
, label2
使用 intrinsicContentSize
,則可以將 label1
的contentHugging
優(yōu)先級設(shè)為低優(yōu)先級,反之,如果我們需要 label1
使用 intrinsicContentSize
,則可以將其 contentHugging
的優(yōu)先級設(shè)為高優(yōu)先級。
那么怎么設(shè)置它們這兩個約束的優(yōu)先級呢?
UIView
提供了兩個方法給我們設(shè)置它的這兩個約束的優(yōu)先級:
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis;
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis;
其中,第一個參數(shù)為優(yōu)先級,具體有以下幾種:
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)先級。
第二個參數(shù)代表你要設(shè)置約束優(yōu)先級的方向,分為橫向和縱向:
typedef NS_ENUM(NSInteger, UILayoutConstraintAxis) {
UILayoutConstraintAxisHorizontal = 0,
UILayoutConstraintAxisVertical = 1
};
以上場景,我們可以通過以下代碼來讓 label1
使用 intrinsicContentSize
, label2
不使用 intrinsicContentSize
:
[label1 setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
效果如下圖: