深刻理解Auto Layout 第一彈

本文轉載至 http://zhangbuhuai.com/2015/07/16/beginning-auto-layout-part-1/

By 張不壞

 2015-07-16 更新日期:2015-07-17html

文章目錄
  1. 1. 寫在前面
  2. 2. iOS佈局機制
  3. 3. 幾個重要的API
    1. 3.1. intrinsicContentSize方法
    2. 3.2. preferredMaxLayoutWidth屬性
    3. 3.3. sizeThatFits:方法和sizeToFit方法
    4. 3.4. systemLayoutSizeFittingSize:方法
    5. 3.5. 對比幾種API
  4. 4. 寫在後面
  5. 5. 參考資料

寫在前面

iOS的的佈局機制「auto layout」不是一個新概念,它早在iOS 6中就推出來了,當下距離iOS 9正式版面世已不遠矣,我卻對它的瞭解還比較初級。ios

我以前對「auto layout」機制的理解很是粗淺,幾乎把它和「constraint」對等。對它的使用有這麼幾個階段:git

  1. Storyboard中經過拖拽設置constraints;
  2. 學習VFL(Visual Format Language)語法使用代碼設置constraints;
  3. 使用大殺器Masonry

P.S:iOS的VFL語法實在太羅嗦了,又臭又長且可讀性差難於調試,沒法忍受,Masonry正是解決這一痛點的第三方庫,很是好用,學習成本很是低。github

近期由於項目,我須要實現一個可以自適應文本自動調整高度的table view cell。網上有相關豐富的資源(category、博客等),思路都很是簡單,好比這篇:動態計算UITableViewCell高度詳解。但即使如此,我對有些東西理解起來還有障礙,譬如systemLayoutSizeFittingSize:sizeThatFits:之類的,想到我都將近有一年的開發經驗了,竟然還沒法「理解」這麼簡單的東西,不能忍!app

致使這種結果的主要緣由有倆:一是以前項目比較簡單,涉及auto layout相關的知識無非是add/update/remove constraints;二是本身過輕浮,把auto layout想得太簡單;less

經過對各類資訊梳理,大概搞明白了本身最大的問題是對auto layout相關的各類API不熟悉或者徹底陌生,這致使了沒法在一些實際問題中使用正確的策略解決問題。本文的着重點正是結合各類資料加上本身的理解對這些API進行分析。
本文涉及的API包括:iview

  • sizeThatFits:sizeToFit
  • systemLayoutSizeFittingSize:
  • intrinsicContentSize

P.S:這幾個API都是與size相關,該如何使用它們呢?這曾讓筆者一時很是困惑。
P.S:以上這幾個API都是UIView的實例方法,除此以外,本文還涉及一些屬性,譬如preferredMaxLayoutWidthide

iOS佈局機制

iOS佈局機制大概分這麼幾個層次:佈局

  • frame layout
  • autoresizing
  • auto layout

frame layout學習

frame layout最簡單直接,簡單來講,即經過設置view的frame屬性值進而控制view的位置(相對於superview的位置)和大小。

autoresizing

autoresizing和frame layout同樣,從一開始存在,它算是後者的補充,基於autoresizing機制,可以讓subview和superview維持必定的佈局關係,譬如讓subview的大小適應superview的大小,隨着後者的改變而改變。

站在代碼接口的角度來看,autoresizing主要體如今幾個屬性上,包括(但不限於):

  1. translatesAutoresizingMaskIntoConstraints
  2. autoresizingMask

第一個屬性標識view是否願意被autoresize;
第二個屬性是一個枚舉值,決定了當superview的size改變時,subview應該作出什麼樣的調整;

關於autoresizing的更詳細使用說明,參考:自動佈局之autoresizingMask使用詳解

auto layout

autoresizing存在的不足是很是顯著的,經過autoresizingMask的可選枚舉值能夠看出:基於autoresizing機制,咱們只能讓view在superview的大小改變時作些調整;而沒法處理兄弟view之間的關係,譬如處理與兄弟view的間隔;更沒法反向處理,譬如讓superview依據subview的大小進行調整。

Auto Layout是隨着iOS 6推出來的,關於它的介紹,官方文檔《Auto Layout Guide》的描述很是精煉:

Auto Layout is a system that lets you lay out your app’s user interface by creating a mathematical description of the relationships between the elements. You define these relationships in terms of constraints either on individual elements, or between sets of elements. Using Auto Layout, you can create a dynamic and versatile interface that responds appropriately to changes in screen size, device orientation, and localization.

簡單來講,它是一種基於約束的佈局系統,能夠根據你在元素(對象)上設置的約束自動調整元素(對象)的位置和大小。

值得一提的是,對於某個view的佈局方式,autoresizing和auto layout只能二選一,簡單來講,若要對某個view採用auto layout佈局,則須要設置其translatesAutoresizingMaskIntoConstraints屬性值爲NO

幾個重要的API

intrinsicContentSize方法

在介紹intrinsicContentSize方法以前,先來看一個應用場景:

場景一:某個UILabel用於顯示單行文本,讓其可以自適應文本,即根據文本自動調整其大小。

讓UILabel自適應文本,在auto layout以前,通常作法是先給定字體,進而計算文本內容所佔據的寬度width和高度height,而後使用得來的width和height設置其frame屬性值。

可是使用auto layout很是簡單,以下:

@interface ViewController () {
UILabel *testLabel;
}
 
- ( void)viewDidLoad {
[ super viewDidLoad];
 
self.view.backgroundColor = [UIColor whiteColor];
 
testLabel = ({
UILabel *label = [[UILabel alloc] init];
label .textAlignment = NSTextAlignmentCenter;
label .font = [UIFont systemFontOfSize:14.0];
label .textColor = [UIColor whiteColor];
label .backgroundColor = [UIColor lightGrayColor];
label;
});
[ self.view addSubview:testLabel];
 
// 使用Masonry添加constraints
[testLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make .top.equalTo(self.view.mas_top).offset(40);
make .left.equalTo(self.view.mas_left).offset(10);
}];
testLabel .text = @"天地玄黃 宇宙洪荒 日月盈昃 辰宿列張";
}
 
- ( void)viewDidAppear:(BOOL)animated {
[ super viewDidAppear:animated];
NSLog(@"testLabel.origin = %@", NSStringFromCGPoint(testLabel.frame.origin));
NSLog(@"testLabel.size = %@", NSStringFromCGSize(testLabel.frame.size));
// print: "testLabel.origin = {10, 40}"
// print: "testLabel.size = {236.5, 17}"
}

效果以下:

問題來了,auto layout system知道testLabel的size呢?

OK,就此引入API intrinsicContentSize

intrinsicContentSize是UIView的基礎方法(Available in iOS 6.0 and later),UIView Class References對它的描述以下:

Returns the natural size for the receiving view, considering only properties of the view itself.
 
Return Value
A size indicating the natural size for the receiving view based on its intrinsic properties.
 
Discussion
Custom views typically have content that they display of which the layout system is unaware. Overriding this method allows a custom view to communicate to the layout system what size it would like to be based on its content. This intrinsic size must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height, for example.
If a custom view has no intrinsic size for a given dimension, it can return UIViewNoIntrinsicMetric for that dimension.

「intrinsic content size」在中文世界裏常被譯做:「固有內容大小」,簡單來講,它被用來告訴auto layout system應該給它分配多大的size

因此呢,在上文代碼(場景一的Solution)中,根據個人理解,layout工做流程是這樣的:在layout時,auto layout system會去回調testLabel的實例方法intrinsicContentSize,該方法可以根據「文本內容+字體」計算出content的size,進而根據此size對testLabel進行佈局。

爲了驗證這個說法,對上述代碼作些改變。

首先定義一個繼承自UILabel的類ZWLabel方法,重寫ZWLabel的intrinsicContentSize方法,以下:

@interface ZWLabel : UILabel
 
@end
 
@implementation ZWLabel
 
- ( CGSize)intrinsicContentSize {
CGSize size = [super intrinsicContentSize];
size .width += 20;
size .height += 20;
return size;
}
 
@end

而後讓上文的testLabel從UILabel的實例改成ZWLabel的實例,其他不變:

testLabel = ({
ZWLabel *label = [[ZWLabel alloc] init];
...
label;
});

效果以下:

效果明顯,本示例較爲直觀說明了intrinsicContentSize這個API的做用了,這個API是爲auto layout system的callback提供的!

P.S:筆者剛開始對intrinsicContentSize這個API的用法感到很是疑惑,在個人理解裏,它有兩種可能:

  1. Auto Layout System會根據content爲view設置一個合適的size,開發者有時須要知道這個size,所以能夠經過intrinsicContentSize獲取;
  2. Auto Layout System在layout時,不知道該爲view分配多大的size,所以回調view的intrinsicContentSize方法,該方法會給auto layout system一個合適的size,system根據此size對view的大小進行設置;

如今看來,第二種理解更靠譜!

對於上文所用到的UILabel,想必Cocoa在實現的intrinsicContentSize方法時已經根據text屬性值和font屬性值進行了計算。那是否是每一個view都實現了intrinsicContentSize呢?

NO!《Auto Layout Guide》在談論「intrinsic content size」時,總會與另一個詞語「leaf-level views」相關聯,譬如:

Intrinsic Content Size
Leaf-level views such as buttons typically know more about what size they should be than does the code that is positioning them. This is communicated through the intrinsic content size, which tells the layout system that a view contains some content that it doesn’t natively understand, and indicates how large that content is, intrinsically.

「leaf-level views」指的是那種通常不包含任何subview的view,譬如UILabel、UIButton等,這類的view每每可以直接計算出content(譬如UILabel的text、UIButton的title,UIImageView的image)的大小。

可是有些view不包含content,譬如view,這種view被認爲「has no intrinsic size」,它們的intrinsicContentSize返回的值是(-1,-1)

P.S:官方文旦中說的是:UIView’s default implementation is to return (UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric),而UIViewNoIntrinsicMetric等於-1,爲何是-1而不是0,我猜是0是一個有效的width/height,而-1不是,更容易區分處理。

還有一種view雖然包含content,可是intrinsicContentSize返回值也是(-1,-1),這類view每每是UIScrollView的子類,譬如UITextView,它們是可滾動的,所以auto layout system在對這類view進行佈局時總會存在一些未定因素,Cocoa乾脆讓這些view的intrinsicContentSize返回(-1,-1)

preferredMaxLayoutWidth屬性

基於上述場景一,咱們來分析更復雜一點的UILabel自適應問題。

場景二:某個UILabel用於顯示多行文本,讓其可以自適應文本,即根據文本自動調整其大小;

對於單行文本UILabel,UILabel的intrinsicContentSize在計算content size時比較容易;但對於多行文本的UILabel,一樣的content,譬如「天地玄黃宇宙洪荒」這八個字,擺放方式能夠是1x8,能夠是2x4,能夠是4x2,auto layout system該如何處理呢?UILabel的屬性preferredMaxLayoutWidth正是用來應對這個問題的。

UILabel Class References對它的描述以下:

The preferred maximum width (in points) for a multiline label.
Discussion
This property affects the size of the label when layout constraints are applied to it. During layout, if the text extends beyond the width specified by this property, the additional text is flowed to one or more new lines, thereby increasing the height of the label.

preferredMaxLayoutWidth的做用顧名思義,用來限制UILabel content size的最大寬度值。以下代碼:

testLabel = ({
UILabel *label = [[UILabel alloc] init];
label .textAlignment = NSTextAlignmentCenter;
label .font = [UIFont systemFontOfSize:14.0];
label .textColor = [UIColor whiteColor];
label .numberOfLines = 0; // mark
label .preferredMaxLayoutWidth = 100; // mark
label .backgroundColor = [UIColor lightGrayColor];
label;
});
[ self.view addSubview:testLabel];
 
// 使用Masonry添加constraints
[testLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make .top.equalTo(self.view.mas_top).offset(40);
make .left.equalTo(self.view.mas_left).offset(10);
}];
 
testLabel .text = @"天地玄黃 宇宙洪荒 日月盈昃 辰宿列張";

效果以下:

那麼最後testLabel的width是否是就是preferredMaxLayoutWidth的屬性值呢?No,最終testLabel的屬性值小於等於preferredMaxLayoutWidth的屬性值。

sizeThatFits:方法和sizeToFit方法

上文已經提到,UITextView繼承自UIScrollView,是能夠滾動的,它的intrinsicContentSize方法返回值是(-1,-1),auto layout system在處理UITextView對象時,爲其設置的size是(0,0)。如此看來,彷佛UITextView沒法體會到auto layout帶來的好處了。

繼續結合應用場景引出sizeThatFits:方法和sizeToFit方法。

場景三:某個UITextView用於顯示文本,讓其可以自適應文本,即根據文本自動調整其大小;

既然UITextView的content計算方法intrinsicContentSize沒法向auto layout system傳遞咱們想要傳達的值,咱們就應該另想別的方法。

好在iOS有直接的接口可供咱們使用。

先談sizeThatFits:方法,UIView Class References對它的描述以下:

Asks the view to calculate and return the size that best fits the specified size.
 
Return Value
A new size that fits the receiver’s subviews.
 
Discussion
The default implementation of this method returns the existing size of the view. Subclasses can override this method to return a custom value based on the desired layout of any subviews. For example, a UISwitch object returns a fixed size value that represents the standard size of a switch view, and a UIImageView object returns the size of the image it is currently displaying.

簡單來講,調用sizeThatFits:方法意味着「根據文本計算最適合UITextView的size」。從功能來說,sizeThatFits:intrinsicContentSize方法比較相似,都是用來計算view的size的。筆者曾一度對兩者的關係很是疑惑,甚至以爲兩者存在相互調用的關係。後來經過驗證發現不是這麼回事兒,後文會經過示例說明。

對於顯示多行文本的UILabel,爲了方便intrinsicContentSize方法更方便計算content size,須要指定preferredMaxLayoutWidth屬性值;對於UITextView的sizeThatFits:,彷佛有相似的需求,畢竟UITextView也可能會顯示多行啊,這樣說來,UITextView也有一個preferredMaxLayoutWidth屬性?

No!preferredMaxLayoutWidth屬性是iOS 6才引入的,sizeThatFits:方法則早得多,何況,UITextView是能夠滾動的,哪怕文本不會所有呈現出來,但也能夠經過左右或者上下滾動瀏覽全部內容;傳給sizeThatFits:的參數(假設爲size)是CGSize類型,size.width的功能和UILabel的preferredMaxLayoutWidth差很少,指定了UITextView區域的最大寬度,size.height則指定了UITextView區域的最大高度;可能有人問,若傳給sizeThatFits:的size小於UITextView.text面積怎麼辦,豈不是有些內容沒法顯示出來?傻啊,能夠滾啊!

值得一提的是,調用sizeThatFits:並不改變view的size,它只是讓view根據已有content和給定size計算出最合適的view.size。

那麼sizeToFit方法是幹嗎的呢?很簡單:

calls sizeThatFits: with current view bounds and changes bounds size.

P.S:有點不太理解,這個「current view」指的是啥?self?仍是superview?
P.P.S:通過驗證,這裏的「current view」指的是self。簡單來講,sizeToFit等價於:

// calls sizeThatFits
CGSize size = [self sizeThatFits:self.bounds.size];
// change bounds size
CGRect bounds = self.bounds;
bounds. size.width = size.width;
bounds. size.height = size.width;

P.S:值得一提的是,通過測試發現,當調用sizeThatFits:size=(width, height),當width/height的值爲0時,width/height彷佛就被認爲是無窮大!

systemLayoutSizeFittingSize:方法

首先來看一個應用場景。

場景四:某個UIView,寬度等於屏幕寬度,包含兩個UILabel,兩個Label均可能顯示多行文本,要求:結合auto layout讓UIView大小可以自適應subviews。

Easy,給出以下代碼:

- ( void)viewDidLoad {
[ super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
 
bgView = ({
UIView *view = [[UIView alloc] init];
view .backgroundColor = [UIColor grayColor];
view;
});
[ self.view addSubview:bgView];
 
label1 = ({
UILabel *label = [[UILabel alloc] init];
label .font = [UIFont systemFontOfSize:14.0];
label .preferredMaxLayoutWidth = self.view.frame.size.width-20;
label .numberOfLines = 0;
label .textColor = [UIColor whiteColor];
label .backgroundColor = [UIColor purpleColor];
label;
});
[bgView addSubview:label1];
 
label2 = ({
UILabel *label = [[UILabel alloc] init];
label .font = [UIFont systemFontOfSize:18.0];
label .preferredMaxLayoutWidth = self.view.frame.size.width-20;
label .numberOfLines = 0;
label .textColor = [UIColor whiteColor];
label .backgroundColor = [UIColor redColor];
label;
});
[bgView addSubview:label2];
 
// 添加約束(基於Masonry)
[bgView mas_makeConstraints:^(MASConstraintMaker *make) {
make .left.equalTo(self.view.mas_left);
make .top.equalTo(self.view.mas_top).offset(10);
make .width.equalTo(self.view.mas_width);
}];
 
[label1 mas_makeConstraints:^(MASConstraintMaker *make) {
make .left.equalTo(bgView.mas_left).offset(10);
make .right.lessThanOrEqualTo(bgView.mas_right).offset(-10);
make .top.equalTo(bgView.mas_top).offset(10);
}];
 
[label2 mas_makeConstraints:^(MASConstraintMaker *make) {
make .left.equalTo(label1.mas_left);
make .right.lessThanOrEqualTo(bgView.mas_right).offset(-10);
make .top.equalTo(label1.mas_bottom).offset(10);
make .bottom.equalTo(bgView.mas_bottom).offset(-10);
}];
 
label1 .text = @"天地玄黃 宇宙洪荒 日月盈昃 辰宿列張 寒來暑往 秋收冬藏 閏餘成歲 律呂調陽";
label2 .text = @"天地玄黃 宇宙洪荒 日月盈昃 辰宿列張 寒來暑往 秋收冬藏 閏餘成歲 律呂調陽";
}

代碼看得有些枯燥,簡單來講,bgView(UIView)中嵌入兩個能顯示多行文本的label1(UILabel)和label2(UILabel),設置約束以下:

代碼運行後的顯示效果:

代碼中除了添加各類各樣的constraints,沒有任何設置frame的代碼,顯然都是基於auto layout的。

那麼問題來了,理解label1和label2的佈局沒啥子問題,由於它們的intrinsicContentSize方法會將content size告訴auto layout system,進然後者會爲它們的size設置對應值;但對於bgView,它但是一個UIView對象,它的intrinsicContentSize回調方法的返回值爲(-1,-1),那麼auto layout system是如何爲它設置合適的size的呢?

根據個人理解,auto layout system在處理某個view的size時,參考值包括:

  • 自身的intrinsicContentSize方法返回值;
  • subviews的intrinsicContentSize方法返回值;
  • 自身和subviews的constraints;


OK,根據筆者理解,結合上圖,我認爲auto layout system是這樣計算一下bgView的size的:
width=max{10+size1.width+10, 10+size2.width+10, size3.width}
height=max{10+size1.height+10+size2.height+10, size3.height}

咱們在viewDidAppear:方法中將相關值打印出來瞧瞧看:

- ( void)viewDidAppear:(BOOL)animated {
[ super viewDidAppear:animated];
 
CGSize size1 = [label1 intrinsicContentSize];
CGSize size2 = [label2 intrinsicContentSize];
CGSize size3 = [bgView intrinsicContentSize];
 
NSLog(@"size1 = %@", NSStringFromCGSize(size1)); // print: "size1 = {300, 33.5}"
NSLog(@"size2 = %@", NSStringFromCGSize(size2)); // print: "size2 = {290.5, 64.5}"
NSLog(@"size3 = %@", NSStringFromCGSize(size3)); // print: "size3 = {-1, -1}"
 
CGSize bgViewSize = bgView.frame.size;
NSLog(@"bgViewSize = %@", NSStringFromCGSize(bgViewSize)); // print: "bgViewSize = {320, 128}"
}

徹底吻合我理解的auto layout size計算公式。

P.S:然而,我知道,事實每每並無這麼簡單,當處理自定義View時,當constraints設置不完整或者衝突時,事情總會變得複雜起來,也總會獲得意想不到的結果。但,暫且就這麼理解吧!

羅莉囉嗦寫了這麼多,還沒引出systemLayoutSizeFittingSize:方法…

OK,再來看另一個應用場景。

場景五:某個UIView,寬度等於屏幕寬度,包含一個UILabel和一個UITextView,兩者均可能顯示多行文本,要求:結合auto layout讓UIView大小可以自適應subviews。

在場景四代碼基礎上將label2改成UITextView對象textView1,以下:

- ( void)viewDidLoad {
[ super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
 
bgView = ({
UIView *view = [[UIView alloc] init];
view .backgroundColor = [UIColor grayColor];
view;
});
[ self.view addSubview:bgView];
 
label1 = ({
UILabel *label = [[UILabel alloc] init];
label .font = [UIFont systemFontOfSize:14.0];
label .preferredMaxLayoutWidth = self.view.frame.size.width-20;
label .numberOfLines = 0;
label .textColor = [UIColor whiteColor];
label .backgroundColor = [UIColor purpleColor];
label;
});
[bgView addSubview:label1];
 
textView1 = ({
UITextView *label = [[UITextView alloc] init];
label .font = [UIFont systemFontOfSize:18.0];
label .textColor = [UIColor whiteColor];
label .backgroundColor = [UIColor redColor];
label;
});
[bgView addSubview:textView1];
 
// 添加約束(基於Masonry)
[bgView mas_makeConstraints:^(MASConstraintMaker *make) {
make .left.equalTo(self.view.mas_left);
make .top.equalTo(self.view.mas_top).offset(10);
make .width.equalTo(self.view.mas_width);
}];
 
[label1 mas_makeConstraints:^(MASConstraintMaker *make) {
make .left.equalTo(bgView.mas_left).offset(10);
make .right.lessThanOrEqualTo(bgView.mas_right).offset(-10);
make .top.equalTo(bgView.mas_top).offset(10);
}];
 
[textView1 mas_makeConstraints:^(MASConstraintMaker *make) {
make .left.equalTo(label1.mas_left);
make .right.lessThanOrEqualTo(bgView.mas_right).offset(-10);
make .top.equalTo(label1.mas_bottom).offset(10);
make .bottom.equalTo(bgView.mas_bottom).offset(-10);
}];
 
label1 .text = @"天地玄黃 宇宙洪荒 日月盈昃 辰宿列張 寒來暑往 秋收冬藏 閏餘成歲 律呂調陽";
textView1 .text = @"天地玄黃 宇宙洪荒 日月盈昃 辰宿列張 寒來暑往 秋收冬藏 閏餘成歲 律呂調陽";
}

運行效果以下:

這顯然不是咱們想要的結果,至少存在這麼兩個問題:

  1. textView1不見了;
  2. bgView的大小不是咱們想要的;

爲何會有出現這樣的問題呢?

首先正如上文所提到的那樣,textView1是UITextView的對象,而UITextView是UIScrollView的子類,它的intrinsicContentSize:方法的返回值是(-1,-1),這意味着textView1對bgView的auto layout size沒有產生影響。

那textView1爲何不見了呢?或者說,爲何textView1的size爲CGSizeZero呢?根據我對auto layout system的理解,auto layout system在處理view的size時,受三個因素影響:

  • 自身的intrinsicContentSize方法返回值;
  • subviews的intrinsicContentSize方法返回值;
  • 自身和subviews的constraints;

對於textView1,前兩個因素能夠忽略掉,簡而言之,textView1的size由它自身的constraints決定。而根據上述代碼的約束能夠計算出textView1的height:
height = bgView.height-10-label1.height-10-10;

而bgView.height=10+label1.height+10+10;意味着textView1的height值爲0;固然就看不到了textView1了。

所以,若想要讓textView1正常可見,至少有這麼一種策略:直接爲textView1添加約束設置width和height;

簡單來講,override回調方法viewWillLayoutSubviews,以下:

- ( void)viewWillLayoutSubviews {
[ super viewWillLayoutSubviews];
 
CGSize textViewFitSize = [textView1 sizeThatFits:CGSizeMake(self.view.frame.size.width-20, 0)];
[textView1 mas_updateConstraints:^(MASConstraintMaker *make) {
make .width.equalTo([NSNumber numberWithFloat:textViewFitSize.width]);
make .height.equalTo([NSNumber numberWithFloat:textViewFitSize.height]);
}];
}

這種作法是有效的,運行效果以下:

若將這種場景切換到table view cell中會如何呢?簡單來講,若是將上述的bgView換成UITableViewCell(或其子類)對象又會如何呢?

在table view中,咱們可使用- (CGFloat)tableView:heightForRowAtIndexPath:接口,該回調方法會返回CGFloat值,該值指示了對應cell的高度;假設auto layout system爲cell分配的size是autoSize,在處理返回值時額外加上textViewFitSize.height便可。

但問題是,咱們如何獲取這個autoSize的值呢?畢竟此時cell還未佈局完成啊,直接讀取cell.frame.size確定是不行的。

systemLayoutSizeFittingSize:方法正是用於處理這個問題的。

UIView Class References對該方法描述以下:

Returns the size of the view that satisfies the constraints it holds.
Return Value
The size of the view that satisfies the constraints it holds.
 
Discussion
Determines the best size of the view considering all constraints it holds and those of its subviews.

我是這麼理解systemLayoutSizeFittingSize:的:對於使用auto layout機制佈局的view,auto layout system會在佈局過程當中綜合各類約束的考慮爲之設置一個size,在佈局完成後,該size的值即爲view.frame.size的值;這包含的另一層意思,即在佈局完成前,咱們是不能經過view.frame.size準確獲取view的size的。但有時候,咱們須要在auto layout system對view完成佈局前就知道它的size,systemLayoutSizeFittingSize:方法正是可以知足這種要求的API。systemLayoutSizeFittingSize:方法會根據其constraints返回一個合適的size值。

systemLayoutSizeFittingSize:方法可傳入一個參數,目前有兩個值能夠傳入:

  • UILayoutFittingCompressedSize : The option to use the smallest possible size.
  • UILayoutFittingExpandedSize : The option to use the largest possible size.

值得一提的是,在使用[view systemLayoutSizeFittingSize:]時,要注意儘可能確保view的constraints的完整性,這樣參數UILayoutFittingCompressedSize和UILayoutFittingExpandedSize獲得的結果是同樣的。不然,舉個例子,若view的right屬性沒有設置,則這兩個參數獲得systemLayoutSizeFittingSize:返回值size是不同的,前者size.width=0,後者size.width=1000。
P.S:這純屬我的使用體驗。

至於systemLayoutSizeFittingSize:的使用場景,動態計算UITableViewCell高度詳解很是值得參考!

對比幾種API

在剛開始接觸這幾個API時感到很是困惑。分不清intrinsicContentSizesizeThatFits:以及systemLayoutSizeFittingSize:的區別。通過這麼將近一天的折騰,如今大概有了基本的判斷。

首先說intrinsicContentSize,它的最主要做用是告訴auto layout system的一些信息,能夠認爲它是後者的回調方法,auto layout system在對view進行佈局時會參考這個回調方法的返回值;通常不多像CGSize size = [view intrinsicContentSize]去使用intrinsicContentSize API。

再來看sizeThatFits:systemLayoutSizeFittingSize:,它們倆很是類似,都是爲開發者直接服務的API(而不是回調方法)。所不一樣的是,sizeThatFits:是auto layout以前就存在的,通常在leaf-level views中用得比較多,在計算size過程當中,它可不會考慮constraints神馬的;對於systemLayoutSizeFittingSize:,它是隨着auto layout(iOS 6)引入的,用於在view完成佈局前獲取size值,若是view的constraints確保了完整性和正確性,一般它的返回值就是view完成佈局以後的view.frame.size的值。

它們以前存在相互調用的關係嗎?通過測試發現,三者以前沒有直接的調用關係。可是能得出這樣的結論:intrinsicContentSize的返回值會直接影響systemLayoutSizeFittingSize:的返回值。至於底層是如何處理的不得而知。

寫在後面

本博客寫了好多個小時,很是勉強,寫完後不忍直視,臭又長。進一步意識到把博客寫長不難,難的是把它寫短同時傳遞足夠多的信息。這在方面,我須要極大的提高啊!

用人話把技術講清楚。

參考資料

相關文章
相關標籤/搜索