使用Masonry(AutoLayout)出現(xiàn)約束沖突的解決方案

大家都知道,系統(tǒng)要展示一個 UIView ,必須要知道它的位置和大小。
在不使用 AutoLayout 的時候,我們通過設(shè)置 frame 屬性來告訴系統(tǒng)這個 UIView 的位置和大小。
如果使用了 AutoLayout ,則需要我們?yōu)?UIView 添加一些約束來讓系統(tǒng)自己計(jì)算 UIView 的位置和大小。

為什么會產(chǎn)生約束沖突

想要解決約束沖突,首先我們要先知道為什么會產(chǎn)生約束沖突。

場景一

假設(shè)我有兩個 UIView ,分別為 view1view2view1 的位置和大小確定, 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é)果如下圖:

1.png

這是一個最簡單的使用 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);
}];

效果如下圖:

2.png

延伸--IntrinsicContentSize

大家可能有時會發(fā)現(xiàn),我們在設(shè)置 UILabel 的約束時,可以不設(shè)置它的大小約束,這樣它就會根據(jù)自己的內(nèi)容(文字)來確定它的大小。如一下場景:

場景三

兩個 UILabel,分別為 label1label2 ,其中 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)行效果如下:

3.png

可以發(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)先級: contentHuggingcontentCompressionResistance

contentHugging(不想變大約束):如果組件的此屬性優(yōu)先級比另一個組件此屬性優(yōu)先級高的話,那么這個組件就保持不變,另一個可以在需要拉伸的時候拉伸。
contentCompressionResistance (不想變小約束):如果組件的此屬性優(yōu)先級比另一個組件此屬性優(yōu)先級高的話,那么這個組件就保持不變,另一個可以在需要壓縮的時候壓縮。

所以,如果我們需要 label1 不使用 intrinsicContentSizelabel2 使用 intrinsicContentSize ,則可以將 label1contentHugging 優(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 使用 intrinsicContentSizelabel2 不使用 intrinsicContentSize

[label1 setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];

效果如下圖:

4.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內(nèi)容