intrinsicContentSize 及 約束優先級/content Hugging/content Compression Resistance

轉自:http://blog.csdn.net/hard_man/article/details/50888377

在了解intrinsicContentSize之前,我們需要先了解2個概念:
AutoLayout在做什么
約束優先級是什么意思。

如果不了解這兩個概念,看intinsic content size沒有任何意義。 注:由于上面這幾個概念都是針對UIView或其子類(UILabel,UIImageView等等)來說的。所以下文中都用UIView指代。
AutoLayout在做什么 – 一個UIView想要顯示在屏幕中,僅須有2個需要確定的元素,一是位置,二是大小。只要2者確定,UIView就可以正確顯示,至于顯示的內容,則由UIView自己決定(drawRect)。


沒有AutoLayout的時候,我們需要通過 initWithFrame:(CGRect)這種方式來指定UIView的位置和大小。
而使用AutoLayout的過程,就是通過約束來確定UIView的位置和大小的過程。
約束優先級 – 為什么約束需要優先級?因為有的時候2個約束可能會有沖突。 比如:有一個UIView 距離父UIView的左右距離都是0,這是2個約束,此時再給此UIView加一個寬度約束,比如指定寬度為100,那么就會產生約束沖突了。

因為,這兩種約束不可能同時存在,只能滿足一個,那么滿足誰呢?默認情況下給UIView加的這幾個約束優先級都是1000,屬于最高的優先級了,表示此約束必須滿足。
所以這種沖突不能被iOS所允許。此時就需要修改優先級了。把其中任意一個約束的優先級改為小于1000的值即可。
iOS可以通過比較兩個”相互沖突的約束”的優先級,從而忽略低優先級的某個約束,達到正確布局的目的。

用鼠標選中寬度約束,然后在屏幕右側的菜單中,修改優先級,如下圖:



這樣就沒有約束沖突了。因為如果一旦兩個約束沖突,系統會自動忽略優先級低的約束。



上面舉的這個例子有些極端,因為上面兩個約束都是確定的值,而且是絕對沖突。所以如果遇到這種情況,可能選擇刪掉某個約束更為合適。
而約束優先級更多的時候用于解決模糊約束(相對于上面的確定值約束來說)的沖突的問題。 比如有這樣一個問題:

UIView1有四個約束:距離父UIView左和上確定,寬和高也確定。
UIView2在UIView1的下面,約束也有4個:上面距離UIView1確定,左面同UIView1對齊,同UIView1等高且等寬。 此時這兩個UIView應該像這樣:


這是一個很普通的應用場景,假設我希望有這樣一個效果: 我希望UIView2的寬度不能超過50。當UIView1寬度小于50的時候,二者等寬;當UIView1寬度大于50的時候,UIView2不受UIView1寬度的影響。 于是我給UIView2加上一條約束:寬度<=50。這時候沖突來了: 因為UIView1的寬度是定好的,而UIView2和UIView1等寬。那么UIView2的寬度就是確定的。
很顯然,分為兩種情況(根據UIView1的寬度不同):
若UIView1的寬度大于50,UIView2的寬度也一定大于50,這跟新加的限制寬度<=50的約束是沖突的。


否則不沖突。


更糟糕的是,實際情況中,UIView1的寬度可能不是一個確定的值。它有可能會被頁面中的其他View所影響,可能還會在運行時產生變化,并不能保證它的實際寬度一定小于50。所以,一旦產生約束沖突,可能就會對應用產生不確定的影響:可能顯示錯亂,也可能程序崩潰。
所以我們為了得到正確的結果,應該這樣處理:
當UIView1寬度小于等于50的時候,約束不沖突,修改優先級與否都是一樣結果。
當UIView1寬度大于50的時候,忽略等寬約束,也就是降低等寬約束優先級。

所以我們把等寬約束的優先級修改為999。上面兩條都滿足,問題解決。



說到模糊約束,content Hugging/content Compression Resistance就是2個UIView自帶的模糊約束。 而這兩個約束存在的條件則是UIView必須指定了 Intrinsic Content Size。 在了解這兩個模糊約束之前,必須了解Intrinsic Content Size是什么東西。

Intrinsic Contenet Size – Intrinsic Content Size:固有大小。顧名思義,在AutoLayout中,它作為UIView的屬性(不是語法上的屬性),意思就是說我知道自己的大小,如果你沒有為我指定大小,我就按照這個大小來。 比如:大家都知道在使用AutoLayout的時候,UILabel是不用指定尺寸大小的,只需指定位置即可,就是因為,只要確定了文字內容,字體等信息,它自己就能計算出大小來。

UILabel,UIImageView,UIButton等這些組件及某些包含它們的系統組件都有 Intrinsic Content Size 屬性。 也就是說,遇到這些組件,你只需要為其指定位置即可。大小就使用Intrinsic Content Size就行了。
在代碼中,上述系統控件都重寫了UIView 中的 -(CGSize)intrinsicContentSize: 方法。 并且在需要改變這個值的時候調用:invalidateIntrinsicContentSize 方法,通知系統這個值改變了。

所以當我們在編寫繼承自UIView的自定義組件時,也想要有Intrinsic Content Size的時候,就可以通過這種方法來輕松實現。

Intrinsic沖突 – 一個UIView有了 Intrinsic Content Size 之后,才可以只指定位置,而不用指定大小。并且才可能會觸發上述兩個約束。 但是問題又來了,對于上述這種UIView來說,只指定位置而不指定大小,有的時候會有問題。 我們用UILabel來舉例吧(所有支持Intrinsic Content Size 的組件都有此問題)。 2個UILabel,UILabel1(文字內容:UILabel1)和UILabel2(文字內容:UILabel2),其內容按照下面說明布局: - 2個UILabel距離上邊欄為50點。 - UILabel1與左邊欄距離為10,UILabel2左面距離UILabel1為10點。 因為都具有Intrinsic屬性,所以不需要指定size。位置應該也明確了。

現在問題來了,再給UILabel2加一條約束,右側距離右邊欄為10點。
很明顯,如果按照約束來布局,則沒辦法滿足2個UIlabel都使用 Intrinsic Content Size,至少某個UILabel的寬度大于Intrinsic Content Size。這種情況,我們稱之為2個組件之間的“Intrinsic沖突”。

解決“Intrinsic沖突”的方案有2種:
兩個UIlabel都不使用Intrinsic Content Size。為兩個UIlabel增加新的約束,來顯式指定它們的大小。如:給2個UIlabel增加寬度和高度約束或等寬等高約束等等。

可以讓其中一個UIlabel使用Intrinsic Content Size,另一個label則自動占用剩余的空間。這時候就需要用到 Content Hugging 和 Content Compression Resistance了!具體做法在下面介紹。

一句話總結“Intrinsic沖突”:兩個或多個可以使用Intrinsic Content Size的組件,因為組件中添加的其他約束,而無法同時使用 intrinsic Content Size了。

content Hugging/content Compression Resistance – 首先,這兩個概念都是UIView的屬性。 假設兩個組件產生了“Intrinsic沖突”: 1. Content Hugging 約束(不想變大約束)表示:如果組件的此屬性優先級比另一個組件此屬性優先級高的話,那么這個組件就保持不變,另一個可以在需要拉伸的時候拉伸。屬性分橫向和縱向2個方向。 2. Content Compression Resistance 約束(不想變小約束)表示:如果組件的此屬性優先級比另一個組件此屬性優先級高的話,那么這個組件就保持不變,另一個可以在需要壓縮的時候壓縮。屬性分橫向和縱向2個方向。 意思很明顯。上面UIlabel這個例子中,很顯然,如果某個UILabel使用Intrinsic Content Size的時候,另一個需要拉伸。 所以我們需要調整兩個UILabel的 Content Hugging約束的優先級就可以啦。 在這個頁面可以調整優先級(拉到最下面)。

分別調整兩個UILabel的 Content Hugging的優先級可以得到不同的結果:




Content Compression Resistance 的情況就不多說了,原理相同。

在代碼中修改UIView的這兩個優先級
[label setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal]; [label setContentCompressionResistancePriority: UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
1
2

Priority是個enum:
typedef float UILayoutPriority;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; // When you send -[UIView systemLayoutSizeFittingSize:], the size fitting most closely to the target size (the argument) is computed. UILayoutPriorityFittingSizeLevel is the priority level with which the view wants to conform to the target size in that computation. It's quite low. It is generally not appropriate to make a constraint at exactly this priority. You want to be higher or lower.

Axis表示橫向及縱向:
typedef NS_ENUM(NSInteger, UILayoutConstraintAxis) { UILayoutConstraintAxisHorizontal = 0, UILayoutConstraintAxisVertical = 1};

創建自定義具有 Intrinsic Content Size 功能的組件
代碼及注釋如下:
//IntrinsicView.h#import <UIKit/UIKit.h>@interface IntrinsicView : UIView@property (nonatomic) CGSize extendSize;@end

//IntrinsicView.m#import "IntrinsicView.h"static bool closeIntrinsic = false;//測試關閉Intrinsic的影響@implementation IntrinsicView- (instancetype)init{ self = [super init]; if (self) { //不兼容舊版Autoreizingmask,只使用AutoLayout //如果為YES,在AutoLayout中則會自動將view的frame和bounds屬性轉換為約束。 self.translatesAutoresizingMaskIntoConstraints = NO; } return self;}//當用戶設置extendSize時,提示系統IntrinsicContentSize變化了。-(void)setExtendSize:(CGSize)extendSize{ _extendSize = extendSize; //如果不加這句話,在view顯示之后(比如延時幾秒),再設置extendSize不會有效果。 //本例中也就是testInvalidateIntrinsic的方法不會產生預期效果。 [self invalidateIntrinsicContentSize];}//通過覆蓋intrinsicContentSize函數修改View的Intrinsic的大小-(CGSize)intrinsicContentSize{ if (closeIntrinsic) { return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric); } else { return CGSizeMake(_extendSize.width, _extendSize.height); }}@end

//測試代碼#import "ViewController.h"#import "newViewCtlViewController.h"#import "IntrinsicView.h"@interface ViewController ()@end@implementation ViewController-(void)viewDidLoad{ [super viewDidLoad]; [self testIntrinsicView];}+-(void) testIntrinsicView{ IntrinsicView *intrinsicView1 = [[IntrinsicView alloc] init]; intrinsicView1.extendSize = CGSizeMake(100, 100); intrinsicView1.backgroundColor = [UIColor greenColor]; [self.view addSubview:intrinsicView1]; [self.view addConstraints:@[ //距離superview上方100點 [NSLayoutConstraint constraintWithItem:intrinsicView1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:100], //距離superview左面10點 [NSLayoutConstraint constraintWithItem:intrinsicView1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:10], ]]; IntrinsicView *intrinsicView2 = [[IntrinsicView alloc] init]; intrinsicView2.extendSize = CGSizeMake(100, 30); intrinsicView2.backgroundColor = [UIColor redColor]; [self.view addSubview:intrinsicView2]; [self.view addConstraints:@[ //距離superview上方220點 [NSLayoutConstraint constraintWithItem:intrinsicView2 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:220], //距離superview左面10點 [NSLayoutConstraint constraintWithItem:intrinsicView2 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:10], ]]; [self performSelector:@selector(testInvalidateIntrinsic:) withObject:intrinsicView2 afterDelay:2];}-(void) testInvalidateIntrinsic:(IntrinsicView *)view{ view.extendSize = CGSizeMake(100, 80);}@end

代碼效果如下:
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容