轉自: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是什么東西。
UILabel,UIImageView,UIButton等這些組件及某些包含它們的系統組件都有 Intrinsic Content Size 屬性。 也就是說,遇到這些組件,你只需要為其指定位置即可。大小就使用Intrinsic Content Size就行了。
在代碼中,上述系統控件都重寫了UIView 中的 -(CGSize)intrinsicContentSize: 方法。 并且在需要改變這個值的時候調用:invalidateIntrinsicContentSize 方法,通知系統這個值改變了。
所以當我們在編寫繼承自UIView的自定義組件時,也想要有Intrinsic Content 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
代碼效果如下: