Auto Layout 的理解

寫在前面

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

我之前對auto layout機制的理解非常粗淺,幾乎把它和「constraint」對等。對它的使用有這么幾個階段:

Storyboard中通過拖拽設置constraints;

學習VFL(Visual Format Language)語法使用代碼設置constraints;

使用大殺器Masonry

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

近期因為項目,我需要實現一個能夠自適應文本自動調整高度的table view cell。網上有相關豐富的資源(category、博客等),思路都非常簡單,比如這篇:動態計算UITableViewCell高度詳解。但即便如此,我對有些東西理解起來還有障礙,譬如systemLayoutSizeFittingSize:和sizeThatFits:之類的,想到我都將近有大半年的開發經驗了,居然還無法理解這么簡單的東西,不能忍!

導致這種結果的主要原因有倆:一是之前項目比較簡單,涉及auto layout相關的知識無非是add/update/remove constraints;二是自己太輕浮,把auto layout想得太簡單;

通過對各種資訊梳理,大概搞明白了自己最大的問題是對auto layout相關的各種API不熟悉或者完全陌生,這導致了無法在一些實際問題中使用正確的策略解決問題。本文的著重點正是結合各種資料加上自己的理解對這些API進行分析。

本文涉及的API包括:

sizeThatFits:和sizeToFit

systemLayoutSizeFittingSize:

intrinsicContentSize

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

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主要體現在幾個屬性上,包括(但不限于):

translatesAutoresizingMaskIntoConstraints

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非常簡單,如下:

@interfaceViewController(){

UILabel*testLabel;

}

- (void)viewDidLoad {

[superviewDidLoad];

self.view.backgroundColor = [UIColorwhiteColor];

testLabel = ({

UILabel*label? ? ? ? = [[UILabelalloc] init];

label.textAlignment? =NSTextAlignmentCenter;

label.font? ? ? ? ? ? = [UIFontsystemFontOfSize:14.0];

label.textColor? ? ? = [UIColorwhiteColor];

label.backgroundColor = [UIColorlightGrayColor];

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 {

[superviewDidAppear: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,就此引入APIintrinsicContentSize。

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方法,如下:

@interfaceZWLabel:UILabel

@end

@implementationZWLabel

- (CGSize)intrinsicContentSize {

CGSizesize = [superintrinsicContentSize];

size.width? +=20;

size.height +=20;

returnsize;

}

@end

然后讓上文的testLabel從UILabel的實例改為ZWLabel的實例,其余不變:

testLabel = ({

ZWLabel *label = [[ZWLabel alloc] init];

//...

label;

});

效果如下:

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

P.S:筆者剛開始對intrinsicContentSize這個API的用法感到非常疑惑,在我的理解里,它有兩種可能:

Auto Layout System會根據content為view設置一個合適的size,開發者有時需要知道這個size,因此可以通過intrinsicContentSize獲取;

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 theintrinsic 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,譬如UIView,這種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? ? ? ? ? ? ? ? = [[UILabelalloc] init];

label.textAlignment? ? ? ? ? =NSTextAlignmentCenter;

label.font? ? ? ? ? ? ? ? ? ? = [UIFontsystemFontOfSize:14.0];

label.textColor? ? ? ? ? ? ? = [UIColorwhiteColor];

label.numberOfLines? ? ? ? ? =0;// mark

label.preferredMaxLayoutWidth =100;// mark

label.backgroundColor? ? ? ? = [UIColorlightGrayColor];

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

CGSizesize = [selfsizeThatFits:self.bounds.size];

// change bounds size

CGRectbounds =self.bounds;

bounds.size.width = size.width;

bounds.size.height = size.width;

self.bounds = bounds;

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

systemLayoutSizeFittingSize:方法

首先來看一個應用場景。

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

Easy,給出如下代碼:

- (void)viewDidLoad {

[superviewDidLoad];

self.view.backgroundColor = [UIColorlightGrayColor];

bgView = ({

UIView*view? ? ? ? = [[UIViewalloc] init];

view.backgroundColor = [UIColorgrayColor];

view;

});

[self.view addSubview:bgView];

label1 = ({

UILabel*label? ? ? ? ? ? ? ? = [[UILabelalloc] init];

label.font? ? ? ? ? ? ? ? ? ? = [UIFontsystemFontOfSize:14.0];

label.preferredMaxLayoutWidth =self.view.frame.size.width-20;

label.numberOfLines? ? ? ? ? =0;

label.textColor? ? ? ? ? ? ? = [UIColorwhiteColor];

label.backgroundColor? ? ? ? = [UIColorpurpleColor];

label;

});

[bgView addSubview:label1];

label2 = ({

UILabel*label? ? ? ? ? ? ? ? = [[UILabelalloc] init];

label.font? ? ? ? ? ? ? ? ? ? = [UIFontsystemFontOfSize:18.0];

label.preferredMaxLayoutWidth =self.view.frame.size.width-20;

label.numberOfLines? ? ? ? ? =0;

label.textColor? ? ? ? ? ? ? = [UIColorwhiteColor];

label.backgroundColor? ? ? ? = [UIColorredColor];

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 {

[superviewDidAppear:animated];

CGSizesize1 = [label1 intrinsicContentSize];

CGSizesize2 = [label2 intrinsicContentSize];

CGSizesize3 = [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}"

CGSizebgViewSize = 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 {

[superviewDidLoad];

self.view.backgroundColor = [UIColorlightGrayColor];

bgView = ({

UIView*view? ? ? ? = [[UIViewalloc] init];

view.backgroundColor = [UIColorgrayColor];

view;

});

[self.view addSubview:bgView];

label1 = ({

UILabel*label? ? ? ? ? ? ? ? = [[UILabelalloc] init];

label.font? ? ? ? ? ? ? ? ? ? = [UIFontsystemFontOfSize:14.0];

label.preferredMaxLayoutWidth =self.view.frame.size.width-20;

label.numberOfLines? ? ? ? ? =0;

label.textColor? ? ? ? ? ? ? = [UIColorwhiteColor];

label.backgroundColor? ? ? ? = [UIColorpurpleColor];

label;

});

[bgView addSubview:label1];

textView1 = ({

UITextView*label? ? = [[UITextViewalloc] init];

label.font? ? ? ? ? ? = [UIFontsystemFontOfSize:18.0];

label.textColor? ? ? = [UIColorwhiteColor];

label.backgroundColor = [UIColorredColor];

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 =@"天地玄黃 宇宙洪荒 日月盈昃 辰宿列張 寒來暑往 秋收冬藏 閏余成歲 律呂調陽";

}

運行效果如下:

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

textView1不見了;

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 {

[superviewWillLayoutSubviews];

CGSizetextViewFitSize = [textView1 sizeThatFits:CGSizeMake(self.view.frame.size.width-20,0)];

[textView1 mas_updateConstraints:^(MASConstraintMaker *make) {

make.width.equalTo([NSNumbernumberWithFloat:textViewFitSize.width]);

make.height.equalTo([NSNumbernumberWithFloat: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時感到非常困惑。分不清intrinsicContentSize、sizeThatFits:以及systemLayoutSizeFittingSize:的區別。經過這么將近一天的折騰,現在大概有了基本的判斷。

首先說intrinsicContentSize,它的最主要作用是告訴auto layout system一些信息,可以認為它是后者的回調方法,auto layout system在對view進行布局時會參考這個回調方法的返回值;一般很少像CGSize size = [view intrinsicContentSize]去使用intrinsicContentSizeAPI。

再來看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:的返回值。至于底層是如何處理的不得而知。

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

推薦閱讀更多精彩內容

  • (一)Masonry介紹 Masonry是一個輕量級的布局框架 擁有自己的描述語法 采用更優雅的鏈式語法封裝自動布...
    木易林1閱讀 2,364評論 0 3
  • Masonry是一個輕量級的布局框架,擁有自己的描述語法,采用更優雅的鏈式語法封裝自動布局,簡潔明了并具有高可讀性...
    3dcc6cf93bb5閱讀 1,782評論 0 1
  • iOS_autoLayout_Masonry 概述 Masonry是一個輕量級的布局框架與更好的包裝AutoLay...
    指尖的跳動閱讀 1,180評論 1 4
  • 原文網址:http://www.cnblogs.com/dingyufenglian/p/4845477.html...
    chen1zee閱讀 9,115評論 0 9
  • 30歲,感覺我的人生已經走完。 我的孩子,如果你出生我會盡心盡力去照顧你但不會溺愛你!在你很小的時候就要好好教育你...
    金令幽蘭閱讀 304評論 0 1