Auto Layout 的理解

寫(xiě)在前面

iOS的的布局機(jī)制「auto layout」不是一個(gè)新概念裳涛,它早在iOS 6中就推出來(lái)了,當(dāng)下距離iOS 9正式版面世已不遠(yuǎn)矣众辨,我卻對(duì)它的了解還比較初級(jí)端三。

我之前對(duì)auto layout機(jī)制的理解非常粗淺,幾乎把它和「constraint」對(duì)等鹃彻。對(duì)它的使用有這么幾個(gè)階段:

Storyboard中通過(guò)拖拽設(shè)置constraints郊闯;

學(xué)習(xí)VFL(Visual Format Language)語(yǔ)法使用代碼設(shè)置constraints;

使用大殺器Masonry蛛株。

P.S: iOS的VFL語(yǔ)法實(shí)在太羅嗦了团赁,又臭又長(zhǎng)且可讀性差難于調(diào)試,無(wú)法忍受谨履,Masonry正是解決這一痛點(diǎn)的第三方庫(kù)欢摄,非常好用,學(xué)習(xí)成本非常低笋粟。

近期因?yàn)轫?xiàng)目怀挠,我需要實(shí)現(xiàn)一個(gè)能夠自適應(yīng)文本自動(dòng)調(diào)整高度的table view cell。網(wǎng)上有相關(guān)豐富的資源(category害捕、博客等)绿淋,思路都非常簡(jiǎn)單,比如這篇:動(dòng)態(tài)計(jì)算UITableViewCell高度詳解尝盼。但即便如此躬它,我對(duì)有些東西理解起來(lái)還有障礙,譬如systemLayoutSizeFittingSize:和sizeThatFits:之類(lèi)的东涡,想到我都將近有大半年的開(kāi)發(fā)經(jīng)驗(yàn)了冯吓,居然還無(wú)法理解這么簡(jiǎn)單的東西,不能忍疮跑!

導(dǎo)致這種結(jié)果的主要原因有倆:一是之前項(xiàng)目比較簡(jiǎn)單组贺,涉及auto layout相關(guān)的知識(shí)無(wú)非是add/update/remove constraints;二是自己太輕浮祖娘,把a(bǔ)uto layout想得太簡(jiǎn)單失尖;

通過(guò)對(duì)各種資訊梳理,大概搞明白了自己最大的問(wèn)題是對(duì)auto layout相關(guān)的各種API不熟悉或者完全陌生渐苏,這導(dǎo)致了無(wú)法在一些實(shí)際問(wèn)題中使用正確的策略解決問(wèn)題掀潮。本文的著重點(diǎn)正是結(jié)合各種資料加上自己的理解對(duì)這些API進(jìn)行分析。

本文涉及的API包括:

sizeThatFits:和sizeToFit

systemLayoutSizeFittingSize:

intrinsicContentSize

P.S: 這幾個(gè)API都是與size相關(guān)琼富,該如何使用它們呢仪吧?這曾讓筆者一時(shí)非常困惑。以上這幾個(gè)API都是UIView的實(shí)例方法鞠眉,除此之外薯鼠,本文還涉及一些屬性择诈,譬如preferredMaxLayoutWidth。

iOS布局機(jī)制

iOS布局機(jī)制大概分這么幾個(gè)層次:

frame layout

autoresizing

auto layout

frame layout

frame layout最簡(jiǎn)單直接出皇,簡(jiǎn)單來(lái)說(shuō)羞芍,即通過(guò)設(shè)置view的frame屬性值進(jìn)而控制view的位置(相對(duì)于superview的位置)和大小。

autoresizing

autoresizing和frame layout一樣郊艘,從一開(kāi)始存在荷科,它算是后者的補(bǔ)充,基于autoresizing機(jī)制纱注,能夠讓subview和superview維持一定的布局關(guān)系步做,譬如讓subview的大小適應(yīng)superview的大小,隨著后者的改變而改變奈附。

站在代碼接口的角度來(lái)看全度,autoresizing主要體現(xiàn)在幾個(gè)屬性上,包括(但不限于):

translatesAutoresizingMaskIntoConstraints

autoresizingMask

第一個(gè)屬性標(biāo)識(shí)view是否愿意被autoresize斥滤;

第二個(gè)屬性是一個(gè)枚舉值将鸵,決定了當(dāng)superview的size改變時(shí),subview應(yīng)該做出什么樣的調(diào)整佑颇;

關(guān)于autoresizing的更詳細(xì)使用說(shuō)明顶掉,參考:自動(dòng)布局之a(chǎn)utoresizingMask使用詳解

auto layout

autoresizing存在的不足是非常顯著的挑胸,通過(guò)autoresizingMask的可選枚舉值可以看出:基于autoresizing機(jī)制痒筒,我們只能讓view在superview的大小改變時(shí)做些調(diào)整;而無(wú)法處理兄弟view之間的關(guān)系茬贵,譬如處理與兄弟view的間隔簿透;更無(wú)法反向處理,譬如讓superview依據(jù)subview的大小進(jìn)行調(diào)整解藻。

Auto Layout是隨著iOS 6推出來(lái)的老充,關(guān)于它的介紹,官方文檔《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.

簡(jiǎn)單來(lái)說(shuō)螟左,它是一種基于約束的布局系統(tǒng)啡浊,可以根據(jù)你在元素(對(duì)象)上設(shè)置的約束自動(dòng)調(diào)整元素(對(duì)象)的位置和大小。

值得一提的是胶背,對(duì)于某個(gè)view的布局方式巷嚣,autoresizing和auto layout只能二選一,簡(jiǎn)單來(lái)說(shuō)钳吟,若要對(duì)某個(gè)view采用auto layout布局廷粒,則需要設(shè)置其translatesAutoresizingMaskIntoConstraints屬性值為NO。

幾個(gè)重要的API

intrinsicContentSize方法

在介紹intrinsicContentSize方法之前砸抛,先來(lái)看一個(gè)應(yīng)用場(chǎng)景:

場(chǎng)景一:某個(gè)UILabel用于顯示單行文本评雌,讓其能夠自適應(yīng)文本树枫,即根據(jù)文本自動(dòng)調(diào)整其大小直焙。

讓UILabel自適應(yīng)文本景东,在auto layout之前,一般做法是先給定字體奔誓,進(jìn)而計(jì)算文本內(nèi)容所占據(jù)的寬度width和高度height斤吐,然后使用得來(lái)的width和height設(shè)置其frame屬性值。

但是使用auto layout非常簡(jiǎn)單厨喂,如下:

@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}"

}

效果如下:

問(wèn)題來(lái)了和措,auto layout system知道testLabel的size呢?

OK蜕煌,就此引入APIintrinsicContentSize派阱。

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

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」在中文世界里常被譯作:「固有內(nèi)容大小」斜纪,簡(jiǎn)單來(lái)說(shuō)贫母,它被用來(lái)告訴auto layout system應(yīng)該給它分配多大的size

所以呢盒刚,在上文代碼(場(chǎng)景一的Solution)中腺劣,根據(jù)我的理解,layout工作流程是這樣的:在layout時(shí)因块,auto layout system會(huì)去回調(diào)testLabel的實(shí)例方法intrinsicContentSize橘原,該方法能夠根據(jù)「文本內(nèi)容+字體」計(jì)算出content的size,進(jìn)而根據(jù)此size對(duì)testLabel進(jìn)行布局涡上。

為了驗(yàn)證這個(gè)說(shuō)法趾断,對(duì)上述代碼做些改變。

首先定義一個(gè)繼承自UILabel的類(lèi)ZWLabel吩愧,重寫(xiě)ZWLabel的intrinsicContentSize方法歼冰,如下:

@interfaceZWLabel:UILabel

@end

@implementationZWLabel

- (CGSize)intrinsicContentSize {

CGSizesize = [superintrinsicContentSize];

size.width? +=20;

size.height +=20;

returnsize;

}

@end

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

testLabel = ({

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

//...

label;

});

效果如下:

效果明顯耻警,本示例較為直觀說(shuō)明了intrinsicContentSize這個(gè)API的作用了隔嫡,這個(gè)API是為auto layout system的callback提供的!

P.S:筆者剛開(kāi)始對(duì)intrinsicContentSize這個(gè)API的用法感到非常疑惑甘穿,在我的理解里腮恩,它有兩種可能:

Auto Layout System會(huì)根據(jù)content為view設(shè)置一個(gè)合適的size,開(kāi)發(fā)者有時(shí)需要知道這個(gè)size温兼,因此可以通過(guò)intrinsicContentSize獲冉盏巍;

Auto Layout System在layout時(shí)募判,不知道該為view分配多大的size荡含,因此回調(diào)view的intrinsicContentSize方法咒唆,該方法會(huì)給auto layout system一個(gè)合適的size,system根據(jù)此size對(duì)view的大小進(jìn)行設(shè)置释液;

現(xiàn)在看來(lái)全释,第二種理解更靠譜!

對(duì)于上文所用到的UILabel误债,想必Cocoa在實(shí)現(xiàn)intrinsicContentSize方法時(shí)已經(jīng)根據(jù)text屬性值和font屬性值進(jìn)行了計(jì)算浸船。那是不是每個(gè)原生view都實(shí)現(xiàn)了intrinsicContentSize呢?

NO寝蹈!《Auto Layout Guide》在談?wù)摗竔ntrinsic content size」時(shí)李命,總會(huì)與另外一個(gè)詞語(yǔ)「leaf-level views」相關(guān)聯(lián),譬如:

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等,這類(lèi)的view往往能夠直接計(jì)算出content(譬如UILabel的text耍鬓、UIButton的title阔籽,UIImageView的image)的大小。

但是有些view不包含content界斜,譬如UIView仿耽,這種view被認(rèn)為「has no intrinsic size」,它們的intrinsicContentSize返回的值是(-1,-1)各薇。

P.S: 官方文檔中說(shuō)的是:UIView’s default implementation is to return (UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric)项贺,而UIViewNoIntrinsicMetric等于-1,為什么是-1而不是0峭判,我猜是0是一個(gè)有效的width/height开缎,而-1不是,更容易區(qū)分處理林螃。

還有一種view雖然包含content奕删,但是intrinsicContentSize返回值也是(-1,-1),這類(lèi)view往往是UIScrollView的子類(lèi)疗认,譬如UITextView完残,它們是可滾動(dòng)的,因此auto layout system在對(duì)這類(lèi)view進(jìn)行布局時(shí)總會(huì)存在一些未定因素横漏,Cocoa干脆讓這些view的intrinsicContentSize返回(-1,-1)谨设。

preferredMaxLayoutWidth屬性

基于上述場(chǎng)景一,我們來(lái)分析更復(fù)雜一點(diǎn)的UILabel自適應(yīng)問(wèn)題缎浇。

場(chǎng)景二:某個(gè)UILabel用于顯示多行文本扎拣,讓其能夠自適應(yīng)文本,即根據(jù)文本自動(dòng)調(diào)整其大小二蓝;

對(duì)于單行文本UILabel誉券,UILabel的intrinsicContentSize在計(jì)算content size時(shí)比較容易;但對(duì)于多行文本的UILabel刊愚,同樣的content踊跟,譬如「天地玄黃宇宙洪荒」這八個(gè)字,擺放方式可以是1x8百拓,可以是2x4琴锭,可以是4x2晰甚,auto layout system該如何處理呢衙传?UILabel的屬性preferredMaxLayoutWidth正是用來(lái)應(yīng)對(duì)這個(gè)問(wèn)題的。

UILabel Class References對(duì)它的描述如下:

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的作用顧名思義厕九,用來(lái)限制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方法

上文已經(jīng)提到,UITextView繼承自UIScrollView畅买,是可以滾動(dòng)的并闲,它的intrinsicContentSize方法返回值是(-1,-1),auto layout system在處理UITextView對(duì)象時(shí)谷羞,為其設(shè)置的size是(0,0)帝火。如此看來(lái),似乎UITextView無(wú)法體驗(yàn)到auto layout帶來(lái)的好處了湃缎。

繼續(xù)結(jié)合應(yīng)用場(chǎng)景引出sizeThatFits:方法和sizeToFit方法犀填。

場(chǎng)景三:某個(gè)UITextView用于顯示文本,讓其能夠自適應(yīng)文本嗓违,即根據(jù)文本自動(dòng)調(diào)整其大芯叛病;

既然UITextView的content計(jì)算方法intrinsicContentSize無(wú)法向auto layout system傳遞我們想要傳達(dá)的值蹂季,我們就應(yīng)該另想別的方法冕广。

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

先談sizeThatFits:方法偿洁,UIView Class References對(duì)它的描述如下:

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.

簡(jiǎn)單來(lái)說(shuō)撒汉,調(diào)用sizeThatFits:方法意味著「根據(jù)文本計(jì)算最適合UITextView的size」。從功能來(lái)講父能,sizeThatFits:和intrinsicContentSize方法比較類(lèi)似神凑,都是用來(lái)計(jì)算view的size的。筆者曾一度對(duì)二者的關(guān)系非常疑惑,甚至覺(jué)得二者存在相互調(diào)用的關(guān)系溉委。后來(lái)通過(guò)驗(yàn)證發(fā)現(xiàn)不是這么回事兒鹃唯,后文會(huì)通過(guò)示例說(shuō)明。

對(duì)于顯示多行文本的UILabel瓣喊,為了方便intrinsicContentSize方法更方便計(jì)算content size坡慌,需要指定preferredMaxLayoutWidth屬性值;對(duì)于UITextView的sizeThatFits:藻三,似乎有類(lèi)似的需求洪橘,畢竟UITextView也可能會(huì)顯示多行啊,這樣說(shuō)來(lái)棵帽,UITextView也有一個(gè)preferredMaxLayoutWidth屬性熄求?

No!preferredMaxLayoutWidth屬性是iOS 6才引入的逗概,sizeThatFits:方法則早得多弟晚,況且,UITextView是可以滾動(dòng)的逾苫,哪怕文本不會(huì)全部呈現(xiàn)出來(lái)卿城,但也可以通過(guò)左右或者上下滾動(dòng)瀏覽所有內(nèi)容;傳給sizeThatFits:的參數(shù)(假設(shè)為size)是CGSize類(lèi)型铅搓,size.width的功能和UILabel的preferredMaxLayoutWidth差不多瑟押,指定了UITextView區(qū)域的最大寬度,size.height則指定了UITextView區(qū)域的最大高度星掰;可能有人問(wèn)多望,若傳給sizeThatFits:的size小于UITextView的text面積怎么辦,豈不是有些內(nèi)容無(wú)法顯示出來(lái)蹋偏?傻啊便斥,可以滾啊威始!

值得一提的是枢纠,調(diào)用sizeThatFits:并不改變view的size,它只是讓view根據(jù)已有content和給定size計(jì)算出最合適的view.size黎棠。

那么sizeToFit方法是干嘛的呢晋渺?很簡(jiǎn)單:

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

P.S:有點(diǎn)不太理解,這個(gè)「current view」指的是啥脓斩?self木西?還是superview?

P.P.S:經(jīng)過(guò)驗(yàn)證随静,這里的「current view」指的是self八千。簡(jiǎn)單來(lái)說(shuō)吗讶,sizeToFit等價(jià)于:

// 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:值得一提的是,經(jīng)過(guò)測(cè)試發(fā)現(xiàn)恋捆,當(dāng)調(diào)用sizeThatFits:的size=(width, height)照皆,當(dāng)width/height的值為0時(shí),width/height似乎就被認(rèn)為是無(wú)窮大沸停!

systemLayoutSizeFittingSize:方法

首先來(lái)看一個(gè)應(yīng)用場(chǎng)景膜毁。

場(chǎng)景四:某個(gè)UIView,寬度等于屏幕寬度愤钾,包含兩個(gè)UILabel瘟滨,兩個(gè)label都可能顯示多行文本栈顷,要求結(jié)合auto layout讓UIView大小能夠自適應(yīng)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 =@"天地玄黃 宇宙洪荒 日月盈昃 辰宿列張 寒來(lái)暑往 秋收冬藏 閏余成歲 律呂調(diào)陽(yáng)";

label2.text =@"天地玄黃 宇宙洪荒 日月盈昃 辰宿列張 寒來(lái)暑往 秋收冬藏 閏余成歲 律呂調(diào)陽(yáng)";

}

代碼看得有些枯燥庐舟,簡(jiǎn)單來(lái)說(shuō),bgView(UIView)中嵌入兩個(gè)能顯示多行文本的label1(UILabel)和label2(UILabel)蠢琳,設(shè)置約束如下:

代碼運(yùn)行后的顯示效果:

代碼中除了添加各種各樣的constraints拭抬,沒(méi)有任何設(shè)置frame的代碼,顯然都是基于auto layout的占业。

那么問(wèn)題來(lái)了,理解label1和label2的布局沒(méi)啥子問(wèn)題纯赎,因?yàn)樗鼈兊膇ntrinsicContentSize方法會(huì)將content size告訴auto layout system谦疾,進(jìn)而后者會(huì)為它們的size設(shè)置對(duì)應(yīng)值;但對(duì)于bgView犬金,它可是一個(gè)UIView對(duì)象念恍,它的intrinsicContentSize回調(diào)方法的返回值為(-1,-1),那么auto layout system是如何為它設(shè)置合適的size的呢晚顷?

根據(jù)我的理解峰伙,auto layout system在處理某個(gè)view的size時(shí),參考值包括:

自身的intrinsicContentSize方法返回值该默;

subviews的intrinsicContentSize方法返回值瞳氓;

自身和subviews的constraints;

OK栓袖,根據(jù)筆者理解匣摘,結(jié)合上圖,我認(rèn)為auto layout system是這樣計(jì)算一下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}

我們?cè)趘iewDidAppear:方法中將相關(guān)值打印出來(lái)瞧瞧看:

- (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計(jì)算公式裹刮。

P.S:然而音榜,我知道,事實(shí)往往并沒(méi)有這么簡(jiǎn)單捧弃,當(dāng)處理自定義View時(shí)赠叼,當(dāng)constraints設(shè)置不完整或者沖突時(shí),事情總會(huì)變得復(fù)雜起來(lái),也總會(huì)得到意想不到的結(jié)果嘴办。但霜第,暫且就這么理解吧!

啰里啰嗦寫(xiě)了這么多户辞,還沒(méi)引出systemLayoutSizeFittingSize:方法…

OK泌类,再來(lái)看另外一個(gè)應(yīng)用場(chǎng)景。

場(chǎng)景五:某個(gè)UIView底燎,寬度等于屏幕寬度刃榨,包含一個(gè)UILabel和一個(gè)UITextView,二者都可能顯示多行文本双仍,要求結(jié)合auto layout讓UIView大小能夠自適應(yīng)subviews枢希。

在場(chǎng)景四代碼基礎(chǔ)上將label2改為UITextView對(duì)象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 =@"天地玄黃 宇宙洪荒 日月盈昃 辰宿列張 寒來(lái)暑往 秋收冬藏 閏余成歲 律呂調(diào)陽(yáng)";

textView1.text =@"天地玄黃 宇宙洪荒 日月盈昃 辰宿列張 寒來(lái)暑往 秋收冬藏 閏余成歲 律呂調(diào)陽(yáng)";

}

運(yùn)行效果如下:

這顯然不是我們想要的結(jié)果朱沃,至少存在這么兩個(gè)問(wèn)題:

textView1不見(jiàn)了苞轿;

bgView的大小不是我們想要的;

為什么會(huì)有出現(xiàn)這樣的問(wèn)題呢逗物?

首先正如上文所提到的那樣搬卒,textView1是UITextView的對(duì)象,而UITextView是UIScrollView的子類(lèi)翎卓,它的intrinsicContentSize:方法的返回值是(-1,-1)契邀,這意味著textView1對(duì)bgView的auto layout size沒(méi)有產(chǎn)生影響。

那textView1為什么不見(jiàn)了呢失暴?或者說(shuō)坯门,為什么textView1的size為CGSizeZero呢?根據(jù)我對(duì)auto layout system的理解逗扒,auto layout system在處理view的size時(shí)古戴,受三個(gè)因素影響:

自身的intrinsicContentSize方法返回值;

subviews的intrinsicContentSize方法返回值矩肩;

自身和subviews的constraints现恼;

對(duì)于textView1,前兩個(gè)因素可以忽略掉蛮拔,簡(jiǎn)而言之述暂,textView1的size由它自身的constraints決定。而根據(jù)上述代碼的約束可以計(jì)算出textView1的height:

height = bgView.height-10-label1.height-10-10;

而bgView.height=10+label1.height+10+10建炫;意味著textView1的height值為0畦韭;當(dāng)然就看不到了textView1了。

因此肛跌,若想要讓textView1正骋张洌可見(jiàn)察郁,至少有這么一種策略:直接為textView1添加約束設(shè)置width和height;

簡(jiǎn)單來(lái)說(shuō)转唉,override回調(diào)方法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]);

}];

}

這種做法是有效的,運(yùn)行效果如下:

若將這種場(chǎng)景切換到table view cell中會(huì)如何呢赠法?簡(jiǎn)單來(lái)說(shuō)麦轰,如果將上述的bgView換成UITableViewCell(或其子類(lèi))對(duì)象又會(huì)如何呢?

在table view中砖织,我們可以使用- (CGFloat)tableView:heightForRowAtIndexPath:接口款侵,該回調(diào)方法會(huì)返回CGFloat值,該值指示了對(duì)應(yīng)cell的高度侧纯;假設(shè)auto layout system為cell分配的size是autoSize新锈,在處理返回值時(shí)額外加上textViewFitSize.height即可。

但問(wèn)題是眶熬,我們?nèi)绾潍@取這個(gè)autoSize的值呢妹笆?畢竟此時(shí)cell還未布局完成啊,直接讀取cell.frame.size肯定是不行的娜氏。

systemLayoutSizeFittingSize:方法正是用于處理這個(gè)問(wèn)題的拳缠。

UIView Class References對(duì)該方法描述如下:

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:的:對(duì)于使用auto layout機(jī)制布局的view,auto layout system會(huì)在布局過(guò)程中綜合各種約束的考慮為之設(shè)置一個(gè)size牍白,在布局完成后脊凰,該size的值即為view.frame.size的值;這包含的另外一層意思茂腥,即在布局完成前,我們是不能通過(guò)view.frame.size準(zhǔn)確獲取view的size的切省。但有時(shí)候最岗,我們需要在auto layout system對(duì)view完成布局前就知道它的size,systemLayoutSizeFittingSize:方法正是能夠滿足這種要求的API朝捆。systemLayoutSizeFittingSize:方法會(huì)根據(jù)其constraints返回一個(gè)合適的size值般渡。

systemLayoutSizeFittingSize:方法可傳入一個(gè)參數(shù)驯用,目前有兩個(gè)值可以傳入:

UILayoutFittingCompressedSize : The option to use the smallest possible size.

UILayoutFittingExpandedSize : The option to use the largest possible size.

值得一提的是蝴乔,在使用[view systemLayoutSizeFittingSize:]時(shí),要注意盡量確保view的constraints的完整性驮樊,這樣參數(shù)UILayoutFittingCompressedSize和UILayoutFittingExpandedSize得到的結(jié)果是一樣的薇正。否則片酝,舉個(gè)例子,若view的right屬性沒(méi)有設(shè)置挖腰,則這兩個(gè)參數(shù)得到systemLayoutSizeFittingSize:返回值size是不一樣的猴仑,前者size.width=0审轮,后者size.width=1000。

P.S:這純屬個(gè)人使用體驗(yàn)榆苞。

至于systemLayoutSizeFittingSize:的使用場(chǎng)景稳衬,動(dòng)態(tài)計(jì)算UITableViewCell高度詳解非常值得參考!

對(duì)比幾種API

在剛開(kāi)始接觸這幾個(gè)API時(shí)感到非常困惑坐漏。分不清intrinsicContentSize薄疚、sizeThatFits:以及systemLayoutSizeFittingSize:的區(qū)別。經(jīng)過(guò)這么將近一天的折騰赊琳,現(xiàn)在大概有了基本的判斷街夭。

首先說(shuō)intrinsicContentSize,它的最主要作用是告訴auto layout system一些信息躏筏,可以認(rèn)為它是后者的回調(diào)方法板丽,auto layout system在對(duì)view進(jìn)行布局時(shí)會(huì)參考這個(gè)回調(diào)方法的返回值;一般很少像CGSize size = [view intrinsicContentSize]去使用intrinsicContentSizeAPI趁尼。

再來(lái)看sizeThatFits:和systemLayoutSizeFittingSize:埃碱,它們倆非常相似,都是為開(kāi)發(fā)者直接服務(wù)的API(而不是回調(diào)方法)酥泞。所不同的是砚殿,sizeThatFits:是auto layout之前就存在的,一般在leaf-level views中用得比較多芝囤,在計(jì)算size過(guò)程中似炎,它可不會(huì)考慮constraints神馬的;對(duì)于systemLayoutSizeFittingSize:悯姊,它是隨著auto layout(iOS 6)引入的羡藐,用于在view完成布局前獲取size值,如果view的constraints確保了完整性和正確性悯许,通常它的返回值就是view完成布局之后的view.frame.size的值仆嗦。

它們之前存在相互調(diào)用的關(guān)系嗎?經(jīng)過(guò)測(cè)試發(fā)現(xiàn)岸晦,三者之前沒(méi)有直接的調(diào)用關(guān)系欧啤。但是能得出這樣的結(jié)論:intrinsicContentSize的返回值會(huì)直接影響systemLayoutSizeFittingSize:的返回值睛藻。至于底層是如何處理的不得而知。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末邢隧,一起剝皮案震驚了整個(gè)濱河市店印,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倒慧,老刑警劉巖按摘,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異纫谅,居然都是意外死亡炫贤,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)付秕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)兰珍,“玉大人,你說(shuō)我怎么就攤上這事询吴÷雍樱” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵猛计,是天一觀的道長(zhǎng)唠摹。 經(jīng)常有香客問(wèn)我,道長(zhǎng)奉瘤,這世上最難降的妖魔是什么勾拉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮盗温,結(jié)果婚禮上藕赞,老公的妹妹穿的比我還像新娘。我一直安慰自己肌访,他們只是感情好找默,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著吼驶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪店煞。 梳的紋絲不亂的頭發(fā)上蟹演,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音顷蟀,去河邊找鬼酒请。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鸣个,可吹牛的內(nèi)容都是我干的羞反。 我是一名探鬼主播布朦,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼昼窗!你這毒婦竟也來(lái)了是趴?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤澄惊,失蹤者是張志新(化名)和其女友劉穎唆途,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體掸驱,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肛搬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了毕贼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片温赔。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鬼癣,靈堂內(nèi)的尸體忽然破棺而出陶贼,到底是詐尸還是另有隱情,我是刑警寧澤扣溺,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布骇窍,位于F島的核電站,受9級(jí)特大地震影響锥余,放射性物質(zhì)發(fā)生泄漏腹纳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一驱犹、第九天 我趴在偏房一處隱蔽的房頂上張望嘲恍。 院中可真熱鬧,春花似錦雄驹、人聲如沸佃牛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)俘侠。三九已至,卻和暖如春蔬将,著一層夾襖步出監(jiān)牢的瞬間爷速,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工霞怀, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惫东,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像廉沮,于是被迫代替她去往敵國(guó)和親颓遏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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

  • (一)Masonry介紹 Masonry是一個(gè)輕量級(jí)的布局框架 擁有自己的描述語(yǔ)法 采用更優(yōu)雅的鏈?zhǔn)秸Z(yǔ)法封裝自動(dòng)布...
    木易林1閱讀 2,334評(píng)論 0 3
  • Masonry是一個(gè)輕量級(jí)的布局框架滞时,擁有自己的描述語(yǔ)法叁幢,采用更優(yōu)雅的鏈?zhǔn)秸Z(yǔ)法封裝自動(dòng)布局,簡(jiǎn)潔明了并具有高可讀性...
    3dcc6cf93bb5閱讀 1,765評(píng)論 0 1
  • iOS_autoLayout_Masonry 概述 Masonry是一個(gè)輕量級(jí)的布局框架與更好的包裝AutoLay...
    指尖的跳動(dòng)閱讀 1,161評(píng)論 1 4
  • 原文網(wǎng)址:http://www.cnblogs.com/dingyufenglian/p/4845477.html...
    chen1zee閱讀 9,106評(píng)論 0 9
  • 30歲漂洋,感覺(jué)我的人生已經(jīng)走完遥皂。 我的孩子,如果你出生我會(huì)盡心盡力去照顧你但不會(huì)溺愛(ài)你刽漂!在你很小的時(shí)候就要好好教育你...
    金令幽蘭閱讀 301評(píng)論 0 1