1.AutoLayout是什么耸别?
在Auto Layout之前,不論是在IB里拖放,還是在代碼中寫件舵,每個UIView都會有自己的frame屬性,來定義其在當(dāng)前視圖中的位置和尺寸脯厨。
UIView *view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 10, 10)];
[self.view addSubview:view];
使用AutoLayout的話铅祸,就變?yōu)榱耸褂眉s束條件來定義view的位置和尺寸。我們用"這個view距離底部10像素合武,距離頂部10像素临梗,距離左邊10像素,距離y右邊像素"或"label A右邊緣和button B左邊緣有20點的空白空間稼跳。"這樣來描述view盟庞。
這樣最大好處是可以解決了不同分辨率和屏幕尺寸下view的適配問題,另外也簡化了旋轉(zhuǎn)時view的位置的定義汤善,原來在底部之上10像素居中的view什猖,不論在旋轉(zhuǎn)屏幕或是更換設(shè)備,始終還在底部之上10像素居中的位置红淡,不會發(fā)生變化不狮。
使用AutoLayout另一個重要的好處就是本地化。比如各自語言中中的文本寬度不同適配起來是一件很麻煩的事在旱。AutoLayout能根據(jù)label需要顯示的內(nèi)容自動改變label的大小摇零。
2.AutoLayout和Autoresizing Mask的區(qū)別
如果你以前一直是代碼寫UI的話,你肯定寫過UIViewAutoresizingFlexibleWidth之類的枚舉桶蝎;如果你以前用IB比較多的話驻仅,一定注意到過每個view的size inspector中都有一個紅色線條的Autoresizing的指示器和相應(yīng)的動畫縮放的示意圖谅畅,這就是Autoresizing Mask。autosizing mask決定了當(dāng)一個視圖的父視圖大小改變時噪服,其自身需要做出什么改變铃彰。
autosizing mask下,你需要為每個view指定各自的寬高芯咧,并添加和父視圖的約束條件:
但AutoLayout則看起來簡單明了多了牙捉,視圖的大小和位置再也不重要了,只有約束要緊敬飒。當(dāng)然邪铲,當(dāng)你拖一個新建的button或label到畫布上時,它會有一定的大小无拗,并且你會將它拖到某一位置带到,但這是只一個用來告訴Interface Builder如何放置約束的設(shè)計工具。
3.開始AutoLayout
我會從一個我們常見的用戶注冊頁面的例子講起
我們希望它在橫屏模式下也可以較好地展示出來英染。
我們在storyBoard中拖拽控件揽惹,注意當(dāng)你拖拽的時候,藍(lán)色虛線將會出現(xiàn)四康。我們應(yīng)該用這些虛線來做向?qū)虏Mㄟ^preview(點擊show the Assistant Editor,并且切換到preview即可開啟)可以看出沒有AutoLayout的時候控件擺放非常糟糕。
首先對于左上方的imageView闪金,我們希望它不管屏幕大小如何哎垦,都保持同樣的大小墨闲,所以我們需要做的是將鼠標(biāo)放在改view上潘酗,按住control鍵,并垂直拖拽日裙。效果如下:
我們點擊Height,這樣就規(guī)定了它的高度固定不變。同樣的道理,我們點擊Weight,這次你需要水平拖拽收苏。
現(xiàn)在我們觀察preview秆乳,你會發(fā)現(xiàn)imageview跑到左上角去了葛闷。并且imageView會出現(xiàn)橙色的線忧陪。為什么呢?因為你的AutoLayout是不完整的阱飘,你只規(guī)定了高度和寬度,你沒有規(guī)定它距離它的父view邊緣的距離是多少缰儿。將鼠標(biāo)放在改view上预麸,按住control鍵,并拖拽至最外面的view上,放開:會看到下面的選項:
Top Space to Superview:是指該兩個view之間保存固定高度
Center Horizontal In Container:是指該兩個view之間垂直居中
Equal Widths:保持相同寬度
Equal Heights:保持相同高度
Aspect Ratio:保存固定比例關(guān)系
很明顯我們需要點擊第一個選項和第二個選項丐巫。第二個選項和imageView自身的width相當(dāng)于規(guī)定了imageView到父view的左右邊緣長度不變。第一個選項和imageView自身的Height相當(dāng)于規(guī)定了imageView到父view的上下高度不變。因此,該imageView的約束條件就完整了。
對于下面的UITextField,我們希望它距離上面的imageView高度固定捂蕴,并且左右邊緣的距離固定昂儒。
我們將UITextField拖拽至imageView,放手如下:
Vertical Spacing:是指兩個view之間的垂直距離固定
Left:是指兩個view左邊對齊
Center X:是指兩個view左邊對齊
Right:是指兩個viewX軸中心對齊
我們需要點擊第一個選項來固定自身和上方imageView之間的距離蒿囤,然后我們需要固定自身和父view邊緣的距離脸侥,所以我們拖拽至父view左右邊緣,如下圖:
Leading Space to Superview:view至父view左邊緣長度(前置距離)固定
Trailing Space to Superview:view至父view右邊緣長度(尾隨距離)固定
我們對第二個UITextField以及下面的UIButton進(jìn)行同樣的操作诡渴。
當(dāng)你對下面的UIButton進(jìn)行同樣的操作后你會發(fā)現(xiàn)仍然出現(xiàn)了表示警告的橙色線恩袱,為什么呢?如果你不知道為什么谅辣,你可以看到Document Outline那里有一個黃色箭頭,點擊它萎河,你會來到下圖所示:
它說:你期望的view高度是30沾鳄,但現(xiàn)在它確實52吞歼,你需要修復(fù)它
你可能懷疑為什么button沒有Width約束,自動布局是為何知道button有多寬(30)的?
事情是這樣的:button自己是知道自己有多寬。它根據(jù)自己的title加上一些padding就行了惩淳。如果你為button的title設(shè)置更大的字號,它會自動調(diào)整它的寬度频敛。
這正是我們熟悉的intrinsic content size任洞。并不是所有的控制器都有這個挪鹏,但大部分是(UILabel是一個例子)。如果一個視圖可以計算自己理想的大小齿梁,那么你就不需要為它特別指定Width或Height約束了催植。但在我們的例子中肮蛹,我希望這個button更高啊,那怎么辦创南?
點擊那個黃色的三角形你會看到:
Update Frame?不伦忠,我們不想它的高度變小。Update Constrains?如果你點擊這個選項的話,你會發(fā)現(xiàn)什么變化都沒有昌渤,因為在它需要你沒有在Height上設(shè)置一個約束條件物咳,也就談不上更新。那我們點擊第三個選項試試赋咽。
可以發(fā)現(xiàn)警告消失了,那我們點擊這個選項之后吨娜,XCode為我們做了什么脓匿?
看這里我們可以發(fā)現(xiàn),UIButton自身增加了一個高度不變的約束條件宦赠,所以警告消失了陪毡。
我們運(yùn)行看看效果如何
豎屏:
橫屏:
嗯,豎屏看起來還不錯勾扭,橫屏看起來不是太好毡琉,UIButton看不見了。這很好理解因為我們固定了控件和父view頂部的距離妙色,但橫屏下高度變小所以UIButton被擠到下面去了桅滋。那怎么辦?如果我們固定了控件和父view底部的距離身辨,很有可能會造成image在橫屏模式下被擠到上面去丐谋,所以有沒有更好的解決辦法呢。
其實上面講到的這些約束條件也是對象栅表,它們是NSLayoutConstraint對象笋鄙,所以我們可以在程序運(yùn)行是動態(tài)改變其中的約束條件,如下圖怪瓶,我們將imageView和父view的垂直距離約束條件拖動到代碼中:
在ViewController.m中寫下以下代碼:
-(void)viewWillLayoutSubviews{
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)){
self.imageViewToViewSpace.constant = 30;
}
else{
self.imageViewToViewSpace.constant = 82;
}
}
效果看上去還不錯:
4.稍微復(fù)雜點的AutoLayout:
我們再將上面那個例子變得稍微復(fù)雜一點萧落,把下面的Button變?yōu)?個button,每個等寬洗贰。
我們首先為button1增加上左右3個距離寬度約束條件:
為button2增加與button1等y軸找岖,與button3距離約束,將button1與button2的距離和button2和button3的距離都設(shè)置為8:
為button3增加與button2等y軸敛滋,與父view的Trailing space:
看起來好多警告许布?恩下面是重點,我們把button1绎晃,button2蜜唾,button3杂曲,設(shè)置為同樣高度和同樣寬度,警告就消失了袁余。
現(xiàn)在運(yùn)行效果如下:
5.SizeClass
我們上面使用了NSLayoutConstraint的IBOutlet對象擎勘,所以我們可以在程序運(yùn)行是動態(tài)改變其中的約束條件,不過有另外一種更優(yōu)雅的方式來實現(xiàn)上面的效果:使用SizeClass颖榜。
隨著iPhone6/iPhone6 Plus的發(fā)布棚饵,現(xiàn)在蘋果生態(tài)圈中的設(shè)備尺寸也已經(jīng)變得種類繁多了。想必蘋果也意識到這一點掩完。都知道蘋果是以化繁為簡的設(shè)計哲學(xué)深入人心的噪漾,這次再一次證明了。SizeClass是對設(shè)備尺寸的一個抽象概念且蓬,現(xiàn)在任何設(shè)備的 長欣硼、寬 被簡潔地分為三種情況:普通 (Regular) 、緊密 (Compact)和任意(Any) 缅疟,這樣分别,根據(jù)長和寬不同的搭配就能產(chǎn)生 3*3=9 種不同尺寸。下圖展示個每種情況對應(yīng)的設(shè)備存淫。
我們可以在不同的屏幕尺寸下使用不同的SizeClass,在正常情況下:
點擊 wAny,hAny可以更改需要布局的尺寸沼填,顯然橫屏的時候桅咆,高度處于壓縮的狀態(tài),(height: compact)坞笙,我們需要先對正常的布局之外岩饼,還要添加一種(wAny, hCompact)
然后我們在這個狀態(tài)下重新設(shè)置我們的布局方式,把上面的imageView的topSpace 修改為10:
你需要知道的是在這個狀態(tài)下的布局方式不會影響其它size下的布局方式薛夜,預(yù)覽效果如下:
你有沒有注意到imageView的圖片不同了呢籍茧,你是不是以為我使用了不同的image?其實是同一張圖片梯澜,只不過我們可以在Images.xcassets上對不同size下使用不同的圖片:
1.AutoLayout與UITableView
你見識到了AutoLayout的強(qiáng)大之處了吧寞冯,下面的例子讓我們把AutoLayout應(yīng)用到UITableView中,嘗試來構(gòu)建更復(fù)雜的應(yīng)用晚伙。例如下面圖片吮龄,當(dāng)UITableView中內(nèi)容不同時,使用AutoLayout來動態(tài)調(diào)整UITableCell的高度咆疗。
我們新建一個UITableViewController的子類漓帚,在我們的storyBoard中添加一個TableViewController,并將它的自定義類設(shè)置為DynamicCellHeightViewController。
在我們的cell上添加如下imageView和Label兩個控件:
新建自定義的cell類CustomTableViewCell并將storyBoard中的cell關(guān)聯(lián)至該類午磁。
并將UILabel的屬性Lines設(shè)為了0以表示顯示多行尝抖。將cell的Identifier設(shè)置為"cell"毡们。
讓我們給這些view一點約束。在上一篇文章你已經(jīng)知道了通過按住ctrl在兩個view之間拖拽增加約束的方式昧辽,此外漏隐,我們還有其他兩種方式:用Editor\Pin和Align菜單:
還有在Interface Builder窗口的底部有一行這樣的按鈕:
從左到右分別是:對齊(Align),固定(Pin)奴迅,解決自動布局問題(Resolve Auto Layout Issues)和重定義尺寸(Resizing Behavior)青责。前三個按鈕魚Editor菜單中的對應(yīng)項有一致的功能。Resizing Behavior按鈕允許你在重新設(shè)置view的尺寸的時候取具,改變已經(jīng)添加的約束脖隶。
頂部的Spacing to nearest neighbor可以添加上下左右四個約束條件,點擊者4個T字架暇检,它們就會變成實體的紅色:
如上圖所示产阱,我們?yōu)閕mageView增加4個約束條件。同理我們?yōu)閘abel增加4個約束條件块仆。
好了构蹬,我們已經(jīng)完成了AutoLayout的布局,下面我們需要實現(xiàn)UITableView的協(xié)議悔据。
先聲明了一個NSArray變量來存放數(shù)據(jù)庄敛。
@interface DynamicCellHeightViewController ()
@property (nonatomic, strong) NSArray *tableData;
@end
@implementation DynamicCellHeightViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableData = @[@"1\n2\n3\n4\n5\n6", @"123456789012345678901234567890", @"1\n2", @"1\n2\n3", @"1"];
}
現(xiàn)在實現(xiàn)UITableViewDataSource的protocol:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.tableData.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
cell.label.text = self.tableData[indexPath.row];
return cell;
}
從self.tableData中的數(shù)據(jù)我們可以看到,每一個Cell顯示的數(shù)據(jù)高度是不一樣的科汗,那么我們需要動態(tài)計算Cell的高度藻烤。由于是自動布局,所以我們需要用到一個systemLayoutSizeFittingSize:來計算UITableViewCell所占空間高度头滔。
這里有一個需要注意的問題怖亭,UITableView是一次性計算完所有Cell的高度,如果有1W個Cell坤检,那么heightForRowAtIndexPath就會觸發(fā)1W次兴猩,然后才顯示內(nèi)容。不過在iOS7以后早歇,提供了一個新方法可以避免這1W次調(diào)用倾芝,它就是estimatedHeightForRowAtIndexPath。要求返回一個Cell的估計值缺前,實現(xiàn)了這個方法蛀醉,那只有顯示的Cell才會觸發(fā)計算高度的protocol. 由于systemLayoutSizeFittingSize需要cell的一個實例才能計算,所以這兒用一個成員變量存一個Cell的實列衅码,這樣就不需要每次計算Cell高度的時候去動態(tài)生成一個Cell實例拯刁,這樣即方便也高效也少用內(nèi)存,可謂一舉三得逝段。
我們聲明一個存計算Cell高度的實例變量:
@property (nonatomic, strong) UITableViewCell *prototypeCell;
然后在viewDidLoad中初始化它:
self.prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:@"cell"];
計算Cell高度的實現(xiàn):
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomTableViewCell *cell = (CustomTableViewCell *)self.prototypeCell;
cell.label.text = [self.tableData objectAtIndex:indexPath.row];
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
NSLog(@"h=%f", size.height + 1);
return 1 + size.height;//加1是因為分隔線的高度
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 66;
}
運(yùn)行效果如下:
恩垛玻,好像哪里不對割捅,計算cell高度時應(yīng)該考慮頭像的高度。
如下圖帚桩,我們?yōu)轭^像和cell之間添加一個約束條件亿驾,并在右面板中將Constant設(shè)置為>=10,這樣cell的最小高度也是頭像高度位置加上10了。
運(yùn)行效果如下:
如果不用systemLayoutSizeFittingSize账嚎,我們也可以手動計算cell的高度莫瞬,只要計算cell中l(wèi)abel的文字高度即可,下面是該方法:
#import "NSString+addition.h"
@implementation NSString (addition)
- (CGSize)calculateSize:(CGSize)size font:(UIFont *)font {
CGSize expectedLabelSize = CGSizeZero;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
NSDictionary *attributes = @{NSFontAttributeName:font, NSParagraphStyleAttributeName:paragraphStyle.copy};
expectedLabelSize = [self boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;
}
else {
expectedLabelSize = [self sizeWithFont:font
constrainedToSize:size
lineBreakMode:NSLineBreakByWordWrapping];
}
return CGSizeMake(ceil(expectedLabelSize.width), ceil(expectedLabelSize.height));
}
@end
像label這種控件會根據(jù)label中text的內(nèi)容自動調(diào)整其高度郭蕉,那如果是UITextView呢疼邀,我們需要下面的方法來返回其大小。
CGSize textViewSize = [cell.label sizeThatFits:CGSizeMake(cell.t.frame.size.width, FLT_MAX)];
2.Self Sizing
在iOS8中引入了一個強(qiáng)大的新特性召锈,Self Sizing.只要在viewDidLoad中加入以下這兩行代碼旁振,然后加入上面的自動布局,我們就可以把計算高度的代碼刪掉了涨岁。
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedRowHeight = 60.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
我自己在項目中也嘗試用過Self Sizing這個特性拐袜,不過當(dāng)tableView加載特別多內(nèi)容時會有明顯的卡頓效果并且tableView有時還會上下跳動!所以我對它的使用保留謹(jǐn)慎態(tài)度梢薪。不過蹬铺,在構(gòu)建簡單的tableView時,這是一個非常好用的特性沮尿。
AutoLayout與ScrollView
ScrollView在AutoLayout上的表現(xiàn)稍微有點特殊丛塌,我們來講講。首先我們拖動一個
ScrollView到視圖中畜疾,并設(shè)置它的約束條件:x , y , width , height.
UIScrollView特殊在于:需要設(shè)置其ContentView!,所以你需要另外拖一個UIView上作為它的內(nèi)容視圖印衔。
并且設(shè)置ContentView對應(yīng)于UIScrollView的Leading Space啡捶、Trailing Space、Top Space奸焙、Bottom Space以及其width瞎暑、height.我設(shè)置Leading Space、Trailing Space与帆、Top Space了赌、Bottom Space都為 0。
在這個例子里玄糟,我們需要內(nèi)容視圖在ScrollView中滑起來勿她,而且只能垂直滑動而不能水平滑動,所以我們需要把ContentView的寬設(shè)置成和ScrollView一樣阵翎,但是高一定要大于ScrollView的高:
你可以在這里下載完整的代碼逢并。如果你覺得對你有幫助之剧,希望你不吝嗇你的star:)