【iOS開發】多屏尺的自動適配 AutoLayout (純程式碼方式),iosautolayout
關於AutoLayout,最早從iOS6開始引入使用。
主要功能是使用約束,對視圖進行相對布局,以適應不同屏尺的變換。 網上大量的資料都在介紹xib和storyboard,如何使用AutoLayout,說純程式碼使用AutoLayout進行UI布局的越來越少。對於我這個習慣了代碼UI布局的人,寫個備忘: AutoLayout是什嗎?使用一句Apple的官方定義的話
AutoLayout是一種基於約束的,描述性的布局系統。 Auto Layout Is a Constraint-Based, Descriptive Layout System.
關鍵詞:
- 基於約束 - 和以往定義frame的位置和尺寸不同,AutoLayout的位置確定是以所謂相對位置的約束來定義的,比如x座標為superView的中心,y座標為螢幕底部上方10像素等
- 描述性 - 約束的定義和各個view的關係使用接近自然語言或者可視化語言(稍後會提到)的方法來進行描述
- 布局系統 - 即字面意思,用來負責介面的各個元素的位置。
總而言之,AutoLayout為開發人員提供了一種不同於傳統對於UI元素位置指定的布局方法。以前,不論是在IB裡拖放,還是在代碼中寫,每個UIView都會有自己的frame屬性,來定義其在當前視圖中的位置和尺寸。使用AutoLayout的話,就變為了使用約束條件來定義view的位置和尺寸。這樣的最大好處是一舉解決了不同解析度和螢幕尺寸下view的適配問題,另外也簡化了旋轉時view的位置的定義,原來在底部之上10像素置中的view,不論在旋轉螢幕或是更換裝置(iPad或者iPhone5或者以後可能出現的mini iPad)的時候,始終還在底部之上10像素置中的位置,不會發生變化。 總結
使用約束條件來描述布局,view的frame會依據這些約束來進行計算 Describe the layout with constraints, and frames are calculated automatically.
AutoLayout和Autoresizing Mask的區別Autoresizing Mask是我們的老朋友了…如果你以前一直是代碼寫UI的話,你肯定寫過UIViewAutoresizingFlexibleWidth之類的枚舉;如果你以前用IB比較多的話,一定注意到過每個view的size inspector中都有一個紅色線條的Autoresizing的指標和相應的動畫縮放的,這就是Autoresizing Mask。在iOS6之前,關於旋轉螢幕的適配和iPhone,iPad螢幕的自動適配,基本都是由Autoresizing Mask來完成的。但是隨著大家對iOS app的要求越來越高,以及已經以及今後可能出現的多種螢幕和解析度的裝置來說,Autoresizing Mask顯得有些落伍和遲鈍了。AutoLayout可以完成所有原來Autoresizing Mask能完成的工作,同時還能夠勝任一些原來無法完成的任務,其中包括:
- AutoLayout可以指定任意兩個view的相對位置,而不需要像Autoresizing Mask那樣需要兩個view在直系的view hierarchy中。
- AutoLayout不必須指定相等關係的約束,它可以指定非相等約束(大於或者小於等);而Autoresizing Mask所能做的布局只能是相等條件的。
- AutoLayout可以指定約束的優先順序,計算frame時將優先按照滿足優先順序高的條件進行計算。
總結
Autoresizing Mask是AutoLayout的子集,任何可以用Autoresizing Mask完成的工作都可以用AutoLayout完成。AutoLayout還具備一些Autoresizing Mask不具備的優良特性,以協助我們更方便地構建介面。
AutoLayout基本使用方法Interface Builder這部分網上大量的教程,都是說的這個
手動使用API添加約束建立iOS6中新加入了一個類:NSLayoutConstraint,一個形如這樣的約束
- item1.attribute = multiplier ? item2.attribute + constant
對應的代碼為
1 |
[NSLayoutConstraint constraintWithItem:button |
2 |
attribute:NSLayoutAttributeBottom |
3 |
relatedBy:NSLayoutRelationEqua |
5 |
attribute:NSLayoutAttributeBottom |
這對應的約束是“button的底部(y) = superview的底部 -10”。添加在建立約束之後,需要將其添加到作用的view上。UIView(當然NSView也一樣)加入了一個新的執行個體方法:
- -(void)addConstraint:(NSLayoutConstraint *)constraint;
用來將約束添加到view。在添加時唯一要注意的是添加的目標view要遵循以下規則:
- 對於兩個同層級view之間的約束關係,添加到他們的父view上
- 對於兩個不同層級view之間的約束關係,添加到他們最近的共同父view上
- 對於有層次關係的兩個view之間的約束關係,添加到層次較高的父view上
重新整理可以通過-setNeedsUpdateConstraints和-layoutIfNeeded兩個方法來重新整理約束的改變,使UIView重新布局。這和CoreGraphic的-setNeedsDisplay一套東西是一樣的~Visual Format Language 可視格式語言UIKit團隊這次相當有愛,估計他們自己也覺得新加約束的API名字太長了,因此他們發明了一種新的方式來描述約束條件,十分有趣。這種語言是對視覺描述的一種抽象,大概過程看起來是這樣的: accept按鈕在cancel按鈕右側預設間距處
最後使用VFL(Visual Format Language)描述變成這樣:
1 |
[NSLayoutConstraint constraintsWithVisualFormat:@\\ "[cancelButton]-[acceptButton]\" |
4 |
views:viewsDictionary]; |
其中viewsDictionary是綁定了view的名字和對象的字典,對於這個例子可以用以下方法得到對應的字典:
1 |
UIButton *cancelButton = ... |
2 |
UIButton *acceptButton = ... |
3 |
viewsDictionary = NSDictionaryOfVariableBindings(cancelButton,acceptButton); |
產生的字典為
{ acceptButton = ""; cancelButton = ""; }
當然,不嫌累的話自己手寫也未嘗不可。現在字典啊數組啊寫法相對簡化了很多了,因此也不複雜。關於Objective-C的新文法,可以參考我之前的一篇WWDC 2012筆記:WWDC 2012 Session筆記——405 Modern Objective-C。 在view名字後面添加括弧以及串連處的數字可以賦予運算式更多意義,以下進行一些舉例:
- [cancelButton(72)]-12-[acceptButton(50)]
- 取消按鈕寬72point,accept按鈕寬50point,它們之間間距12point
- [wideView(>=60@700)]
- wideView寬度大於等於60point,該約束條件優先順序為700(優先順序最大值為1000,優先順序越高的約束越先被滿足)
- V:[redBox][yellowBox(==redBox)]
- 豎直布局,先是一個redBox,其下方緊接一個寬度等於redBox寬度的yellowBox
- H:|-[Find]-[FindNext]-[FindField(>=20)]-|
- 水平布局,Find距離父view左邊緣預設間隔寬度,之後是FindNext距離Find間隔預設寬度;再之後是寬度不小於20的FindField,它和FindNext以及父view右邊緣的間距都是預設寬度。(豎線'|‘ 表示superview的邊緣)
容易出現的錯誤因為涉及約束問題,因此約束模型下的所有可能出現的問題這裡都會出現,具體來說包括兩種:
- Ambiguous Layout 布局不能確定
- Unsatisfiable Constraints 無法滿足約束
布局不能確定指的是給出的約束條件無法唯一確定一種布局,也即約束條件不足,無法得到唯一的布局結果。這種情況一般添加一些必要的約束或者調整優先順序可以解決。無法滿足約束的問題來源是有約束條件互相衝突,因此無法同時滿足,需要刪掉一些約束。兩種錯誤在出現時均會導致布局的不穩定和錯誤,Ambiguous可以被容忍並且選擇一種可行布局呈現在UI上,Unsatisfiable的話會無法得到UI布局並報錯。 對於不能確定的布局,可以通過調試時暫停程式,在debugger中輸入
- po [[UIWindow keyWindow] _autolayoutTrace]
來檢查是否存在Ambiguous Layout以及存在的位置,來協助添加條件。另外還有一些檢查方法,來查看view的約束和約束狀態:
- [view constraintsAffectingLayoutForOrientation/Axis: NSLayoutConstraintOrientationHorizontal/Vertical]
- [view hasAmbiguousLayout]
- [view exerciseAmbiguityInLayout]
布局動畫動畫是UI體驗的重要部分,更改布局以後的動畫也非常關鍵。說到動畫,Core Animation又立功了..自從CA出現以後,所有的動畫效果都非常cheap,在auto layout中情況也和collection view裡一樣,很簡單(可以參考WWDC 2012 Session筆記——219 Advanced Collection Views and Building Custom Layouts),只需要把layoutIfNeeded放到animation block中即可~
1 |
[UIView animateWithDuration:0.5 animations:^{ |
部分代碼純淨代碼UI正常布局後,添加autolayout就可以了,調整相當方便這是一段水平置中,垂直並列的4個按鈕 布局代碼setTranslatesAutoresizingMaskIntoConstraints 是為no,開啟AutoLayou.
//-----autoLayout
[_btn_1 setTranslatesAutoresizingMaskIntoConstraints:NO];
[_btn_2 setTranslatesAutoresizingMaskIntoConstraints:NO];
[_btn_3 setTranslatesAutoresizingMaskIntoConstraints:NO];
[_btn_4 setTranslatesAutoresizingMaskIntoConstraints:NO];
CGSize winSize = [[iHappySDKSingle shareSingle] getScreenSize];
CGFloat tpo = _btn_1.frame.origin.y;
CGFloat hpod = _btn_1.frame.origin.x;
CGFloat btnH = _btn_1.frame.size.height;
CGFloat vpod = winSize.width*0.15-btnH;
NSNumber* tp = [NSNumber numberWithFloat:tpo];
NSNumber* hd = [NSNumber numberWithFloat:hpod];
NSNumber* vd = [NSNumber numberWithFloat:vpod];
NSNumber* bh = [NSNumber numberWithFloat:btnH];
NSNumber* btm = [NSNumber numberWithFloat:vpod*2];
NSDictionary *dict1 = NSDictionaryOfVariableBindings(_btn_1,_btn_2,_btn_3,_btn_4);
NSDictionary *metrics =@{@"hPadding":hd,@"vPadding":vd,@"top":tp,@"btm":btm,@"btnHeight":bh};
NSString *vfl1 = @"|-hPadding-[_btn_1]-hPadding-|";
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vfl1
options:0
metrics:metrics
views:dict1]];
NSString *vfl2 = @"|-hPadding-[_btn_2]-hPadding-|";
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vfl2
options:0
metrics:metrics
views:dict1]];
NSString *vfl3 = @"|-hPadding-[_btn_3]-hPadding-|";
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vfl3
options:0
metrics:metrics
views:dict1]];
NSString *vfl4 = @"|-hPadding-[_btn_4]-hPadding-|";
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vfl4
options:0
metrics:metrics
views:dict1]];
NSString *vfl5 = @"V:|-(<=top)-[_btn_1(btnHeight)]-vPadding-[_btn_2(btnHeight)]-vPadding-[_btn_3(btnHeight)]-vPadding-[_btn_4(btnHeight)]-(>=btm)-|";
if (_btn_1.hidden) {
vfl5 = @"V:|-(<=top)-[_btn_2(btnHeight)]-vPadding-[_btn_3(btnHeight)]-vPadding-[_btn_4(btnHeight)]-(>=btm)-|";
}
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vfl5
options:0
metrics:metrics
views:dict1]];
純淨代碼UI正常布局後,增加一個函數,進行自動布局水平置中布局:NSLayoutAttributeCenterX垂直置中布局:NSLayoutAttributeCenterY以及後面的布局切換動畫。
- (void)setAutoLayoutForKuang:(UIView*)imgv
{
UIView * view = self;
[imgv setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *dict1 = NSDictionaryOfVariableBindings(imgv);
NSDictionary *metrics = @{@"width":[NSNumbernumberWithFloat:imgv.frame.size.width],
@"height":[NSNumber numberWithFloat:imgv.frame.size.height],
@"top":[NSNumber numberWithFloat:imgv.frame.origin.y]
};
NSString *vfl1 = @"[imgv(width)]";
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vfl1
options:0
metrics:metrics
views:dict1]];
NSString *vfl2 = @"V:[imgv(height)]";
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vfl2
options:0
metrics:metrics
views:dict1]];
[view addConstraint:[NSLayoutConstraint constraintWithItem:imgvattribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:viewattribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
[view addConstraint:[NSLayoutConstraint constraintWithItem:imgvattribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:viewattribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
//animation
[UIView animateWithDuration:0.25 animations:^{
[imgv layoutIfNeeded];
}];
}
-----------------------------------------網上相關文章:-----------------------------------------1、AutoLayout(自動布局)入門 推薦2、Autolayout及VFL經驗分享 此文包含有一個demo(快速存取下載) 推薦3、iOS 6 Auto Layout NSLayoutConstraint 介面布局 不是朋友在發愁手動些這些類似指令碼的字元:現在推薦一個開源庫給大家github:https://github.com/TelenLiu/Lytgit@OSC: http://git.oschina.net/TelenLiu/Lyt