SizeClass Autolayout 在Storyboard或XB上的適配
現在蘋果生態圈中的設備尺寸也已經變得種類繁多了,設備種類如下:
iPad:iPad1,iPad2,newPad,iPad4,iPad air,iPad air2,iPad mini1,iPad mini2,iPad mini3,iPad pro;
iPhone:iPhone3Gs,iPhone4,iPhone4S,iPhone5,iPhone5S,iPhone5C iPhone6,iPhone6 Plus,iPhone6S,ipad iPhone6S Plus;
iWatch
屏幕大小:
- iPhone:3.5,4.0,4.7,5.5
- iPad: 7.9,9.7,12.9
- iWatch:1.6,1.8
想必蘋果也意識到這一點。都知道蘋果是以化繁為簡的設計哲學深入人心的,這次再一次證明了。SizeClass是對設備尺寸的一個抽象概念,現在任何設備的 長、寬 被簡潔地分為三種情況:普通 (Regular) 、緊密 (Compact) 和 任意(Any) ,這樣,根據長和寬不同的搭配就能產生 3 * 3 = 9 種不同尺寸。下圖展示個每種情況對應的設備。
以前為不同的iOS設備尺寸或者同尺寸橫豎屏不同適配UI,都要根據實際情況而去計算frame。使用Size Classes是根據當前的屏幕size類型而使用Auto Layout方式進行布局了,要摒棄之前計算frame的思路,而改用相對布局的思維去思考(實際上還是要計算frame).
而且Xcode6最大的突破也是這里,不在需要建立不同尺寸的storyboard了,只要建立一個,然后修改其view的size就可以做各種屏幕尺寸的適配,如下:
例如我要做iPad頁面設計,就設置成w (Regular)/h(Regular)
然后同樣的工程,又要兼容橫屏的iPhone6 plus,就可以把view的size class設置成:w (Regular)/h(Compact),然后繼續適配
然后當程序跑在不同的設備上,或者設備橫屏和豎屏切換,就能顯示相應的UI了。
示例:
適配iPhone6,在RootViewController的view上添加一個新的view,讓這個新的view無論屏幕橫屏還是豎屏時候都距離其superview的邊緣50點寬,并且橫屏時候為綠顏色,豎屏時候為紅顏色。
操作如下:
-
切換size class為wCompact/hRegular模式
并且添加一個view,不用管其frame,并設置其背景色為紅色
接下來選中紅色的view,然后點擊Xcode頂部工具欄的Editor-Pin,然后分別添加紅色view相對superview邊框的約束(上下左右
添加約束的過程中會看到約束的線是黃顏色,表明當前的約束還不能確定view的frame,需要繼續添加,當添加完4個約束后,約束線的顏色是藍色的,表明當前約束是正確的。
然后選中約束,設定約束的值(我們不是想讓新的view距離其superview邊界50點寬嗎!),4個約束都要設置。
設置完后點擊下view會自動更新frame,應該是這樣的:
2.切換size class為wRegular/hCompact模式,然后重復第一步中的設置,區別是新添加的view背景顏色設置為綠色。
示例2:
基于iPhone適配界面,添加三個view到rootView上,然后無論橫屏還是豎屏,新添加的三個view之間及與屏幕邊框的距離都保持不變的間距20點寬,效果如圖:
因為要適配iPhone橫豎屏,所以修改size class為wCompact/hRegular來適配豎屏:拖拽3個view到rootView上,并設置其背景顏色
為了滿足設計要求,要添加如下constraint:
- 設定綠色view距離superview左邊距和上邊距;
- 設定黃色view距離superview右邊距和上邊距,相對綠色view的的左邊距;
- 設定藍色view的左邊距和右邊距和下邊距,上邊距離綠色view下邊的距離;
- 設定綠色view與黃色view等寬高
- 設定藍色view與綠色view等高
操作如下:
選中綠色view,Eidtor->Pin->Leading Space to Superview給綠色view添加相對其superview的左邊距,然后選中constraint,修改約束的值為20,其他constraint以此類推
添加完如圖:
其中紅色框部分清晰的表達了所添加的constraint;藍色框部分時添加的constraint,目前為黃色線,表明當前的constraint還不能定位view,當一個view的constraint正確的時候,constraint的顏色會變為藍色。綠色線框的部分表達了constraint的數值,我們想讓邊距為20,所以設置數值為20 。wC hR Installed表明當前constraint適用于wC hR這種size class,不適合any any的size class。
添加綠色view與黃色view之前的距離時候,由于是設定兩個子view的constraint,所以要選中兩個view,然后Editor->Pin ->Horizontal,設定值為20:
同樣方法Editor->Pin ->Width Equally,設定綠色view與黃色view等寬度,藍色view與綠色view等高,結果如圖:
但發現constraint顏色仍然后黃色,原因是當前view的位置和constraint希望的不一致,更新下frame(要選中3個view,因為constraint關聯3個view)或者點擊Document Outline中的黃色小箭頭,然后會看到具體的constraint信息來一步步調試,這個也是Xcode6最有突破的地方:
然后效果如圖:
然后運行下項目吧,發現確實和預期的一樣。然后旋轉屏幕,是不是發現橫屏時候白了,屏幕什么都沒有了?原因是我們僅僅適配的豎屏,橫屏還沒有適配啊!
修改size class,iPhone4s橫屏的size class為wCompact/hCompact,而iPhone6 plus為wReguage/hCompact,那我們不如設置為wAny/hCompact吧!然后安裝上邊適配豎屏的方式適配橫屏。適配好后再次運行,橫豎屏都應該是我們想要的了。
小小技巧:
查看不同設備適配情況
Autolayout 純代碼的適配
在 ios6 之前沒有AutoLayout布局UI,我們布局UI是基于固定的frame,bound對控件的布局,設置控件的 位置(x,y), 尺寸(width,height)就可以把控件放在相應的位置。
出現Autolayout后,我們用AutoLayout布局控件就要把之前固定設置frame、bound忘記;要想布局顯示一個控件,Autolayout以兩個詞:約束,參照 動態設置一個控件兩個東西,位置,尺寸;
小結:
1. 添加約束不宜過多,當添加的約束足以表達該控件的位置與尺寸,就足夠了
2. 約束就是對控件的大小或者位置進行約束,參照就是以某個控件的位置進行約束,其實這兩者沒有明確的分別,它們都可以對控件的位置與尺寸起到作用。
兩種語法
1)手動添加約束(蘋果官方API)
/**
* 這個是系統默認添加約束的方法,它是NSLayoutConstraint的類方法
*
* @param view1 傳入想要添加約束的控件
* @param attr1 傳入想要添加約束的方向,這個枚舉值有很多,可以自己看看
* @param relation 傳入與約束值的關系,大于,等于還是小于
* @param view2 傳入被參照對象
* @param attr2 傳入被參照對象所被參照的方向,如頂部,左邊,右邊等等
* @param multiplier 傳入想要的間距倍數關系
* @param c 傳入最終的差值
*
* @return NSLayoutConstraint對象
*/
+(instancetype)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c
//一部分NSLayoutAttribute枚舉值
NSLayoutAttributeLeft = 1,//控件左邊
NSLayoutAttributeRight,//控件右邊
NSLayoutAttributeTop,
NSLayoutAttributeBottom,
NSLayoutAttributeLeading,//控件左邊
NSLayoutAttributeTrailing,//控件右邊
NSLayoutAttributeWidth,//控件的寬
NSLayoutAttributeHeight,//控件的高
NSLayoutAttributeCenterX,//豎直方向中點
NSLayoutAttributeCenterY,//水平方向中點
這個方法的參數我很想用形象的語言描述出來,但是還是想不出,更多需要大家從下面的代碼中傳入的參數去體會
//創建redView
UIView *redView = [[UIView alloc]init];
redView.backgroundColor = [UIColor redColor];
redView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:redView];
//創建redView第一個約束,相對self.view的左邊緣間距20
NSLayoutConstraint * redLeftLc = [NSLayoutConstraint constraintWithItem:redView
attribute:NSLayoutAttributeLeftMargin
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0f
constant:20.0];
//只有在沒有參照控件的情況下,約束才加到自身,不然加到父控件上
[self.view addConstraint:redLeftLc];
//創建redView第二個約束,相對self。view的底邊緣間距20
NSLayoutConstraint *redBottomLc = [NSLayoutConstraint constraintWithItem:redView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottomMargin
multiplier:1.0f
constant:-20];//由于是redview相對self.view往上減20,所以是-20
//添加約束
[self.view addConstraint:redBottomLc];
//這里直接設置自身寬為50
NSLayoutConstraint * redWLc = [NSLayoutConstraint constraintWithItem:redView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:kNilOptions
multiplier:1.0f
constant:50.0f];
//由于沒有參照物,所以約束添加于自身身上
[redView addConstraint:redWLc];
//創建最后一個約束,自身的高
NSLayoutConstraint * redHLc = [NSLayoutConstraint constraintWithItem:redView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:kNilOptions
multiplier:1.0f
constant:50];
//由于沒有參照物,所以約束添加于自身身上
[redView addConstraint:redHLc];
運行的效果圖
接下來我們繼續增加需求,在紅色方塊的右邊放一個離它20間距,離self.view底部也間距20,寬高相等的藍色方塊
//先創建一個一個藍色的view添加到視圖上,剩下的就是用autolayout來設置它的“frame”了
UIView *blueView = [[UIView alloc]init];
blueView.backgroundColor = [UIColor blueColor];
blueView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:blueView];
self.blueView = blueView;
//創建第一個約束,左邊間距,由于是想要與紅色有20的間距,那么參照參數“toItem”就應該填redView
NSLayoutConstraint *blueLeft = [NSLayoutConstraint constraintWithItem:blueView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeRight
multiplier:1.0f
constant:20.0f];
//與其他控件發生約束,所以約束添加到父控件上
[self.view addConstraint:blueLeft];
//現在我們已經可以確定自己水平方向的位置了,還差垂直方向的位置,現在我們來創建第二個約束,參照物依然是紅色方塊,需求是要離self.view底部20間距,這不是正好和紅色一樣么,那么我們可以直接與紅色方塊底部對齊就行了
NSLayoutConstraint *blueBottom = [NSLayoutConstraint constraintWithItem:blueView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:0.0f];//與紅色方塊底部對齊,倍數1.0f.差值0.0f
//與其他控件發生約束,所以約束添加到父控件上
[self.view addConstraint:blueBottom];
//剩下兩個約束差不多,我就一并描述了,它們都以redView為參照,與其等寬等高
NSLayoutConstraint *blueW = [NSLayoutConstraint constraintWithItem:blueView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f];
[self.view addConstraint:blueW];
NSLayoutConstraint *blueH = [NSLayoutConstraint constraintWithItem:blueView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeHeight
multiplier:1.0f
constant:0.0f];
[self.view addConstraint:blueH];
接下來看看效果圖
小結:
1.其實Autolayout的思想非常簡單,剛開始使用的時候不要想著馬上一氣呵成,最好一個控件一個控件的實現依賴,分別滿足其位置與尺寸的需求,如果一下子幾個控件一起弄的話,往往大家犯錯是犯在約束添多了,而不是添少了。
2.就如上面的例子,很多人會在設置了與紅色等高等寬后,還同時去添加頂部對齊與底部對齊,這樣高度就重復設置了,他會忽略了,上下同時對齊不僅給予了垂直位置,也給予了高度,所以思路必須清晰!
Autolayout 下添加動畫
我將在藍色方塊的右邊再加個同樣大小的黃色方塊,然后,要求點擊屏幕,然后藍色方塊被移除,黃色方塊替代藍色方塊的位置
還有一個autolayout的知識點:優先級(priority)
//一如往常,先創建黃色View
UIView *yellowV = [[UIView alloc]init];
yellowV.backgroundColor = [UIColor yellowColor];
yellowV.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:yellowV];
//開始創建約束,第一個約束是什么呢?一看就知道是左間距約束啦
NSLayoutConstraint *yellowLeft = [NSLayoutConstraint constraintWithItem:yellowV
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:blueView attribute:NSLayoutAttributeRight
multiplier:1.0f
constant:20];
//與其他控件發生約束,所以約束添加到父控件上
[self.view addConstraint:yellowLeft];
//添加底部約束
NSLayoutConstraint *yellowBottom = [NSLayoutConstraint constraintWithItem:yellowV
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:-20];
//與其他控件發生約束,所以約束添加到父控件上
[self.view addConstraint:yellowBottom];
//這里我直接設置寬高約束了,就省事不加參照控件了
NSLayoutConstraint *yellowW = [NSLayoutConstraint constraintWithItem:yellowV
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:kNilOptions
multiplier:1.0f
constant:50.0f];
[yellowV addConstraint:yellowW];
NSLayoutConstraint *yellowH = [NSLayoutConstraint constraintWithItem:yellowV
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:kNilOptions
multiplier:1.0f
constant:50.0f];
[yellowV addConstraint:yellowH];
運行效果
接下來我再給黃色View添加一個約束,這個約束涉及到優先級,大家注意看代碼了哈
//對黃色View添加約束,約束黃色view與紅色View的間距為20
NSLayoutConstraint *yellowAnotherLeft = [NSLayoutConstraint constraintWithItem:yellowV
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeRight
multiplier:1.0f
constant:20];
UILayoutPriority priority = 250;//設置優先級
yellowAnotherLeft.priority = priority;
//與其他控件發生約束,所以約束添加到父控件上
[self.view addConstraint:yellowAnotherLeft];
想必大家應該看出些端倪了,我在前面已經給黃色View添加了對藍色View間距位20的view,現在又給黃色view對紅色View添加一個間距20的約束,這很明顯是不可能出現的情況,黃色View怎么可能同時做到這兩個約束呢,用術語來說就是約束沖突,但是大家注意看這段代碼
UILayoutPriority priority = 250;//設置優先級
我給yellowAnotherLeft這個約束添加了優先級,優先級的范圍是0~1000,數字越大,優先級越高,在不設置的情況下默認為1000
這說明了,我最后添加的這個約束的優先級是低的,這個約束只有在它的沖突約束被抹掉后,它才能實現
也就是說,我把藍色view移除后,黃色View相對于藍色View左間距20這個約束就不成立了,那么黃色view會自動的變為與紅色View間距20
讓我們最后加幾行代碼,來實現這個動畫吧!
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//先把藍色方塊從父視圖上移除
[self.blueView removeFromSuperview];
//動畫更新界面
[UIView animateWithDuration:1.0f animations:^{
[self.view layoutIfNeeded];
}];
}
autolayout的動畫就是這樣實現的,將操作代碼走完后,再讓動畫塊去更新界面,動畫就出來了,效果如下:
2)VFL(visual format language)
需求:適配iPhone6,在RootViewController的view上添加一個新的view,讓這個新的view無論屏幕橫屏還是豎屏時候都距離其superview的邊緣20
H:|-20-[newView]-20-|
V:|-20-[newView]-20-|
每一個 [] 中表示一個視圖或一個控件;V 表示垂直約束,H 表示水平約束,然后兩個視圖之間的 -0- 的意思就很簡單了,意思就是這兩個視圖之間間隔為0
比如:
V:[view0(67)]-0-[view1(67)]-0-[view2(67)]-0-[view3(67)]-0-[view4(67)]-0-|
self.view.translatesAutoresizingMaskIntoConstraints = NO;
UIView *newView = [UIView new];
newView.backgroundColor = [UIColor greenColor];
[self.view addSubview:newView];
newView.translatesAutoresizingMaskIntoConstraints = NO;
NSMutableArray *constraintArray = [NSMutableArray array];
[constraintArray addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[newView]-20-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(newView,self.view)]];
[constraintArray addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-20-[newView]-20-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(newView,self.view)]];
[self.view addConstraints:constraintArray];
運行效果
第三方庫 Masonry
- VFL的約束創建非常宏觀,如果既要照顧語法講解,又要照顧約束理解.
- Masonry的約束添加思維其實與蘋果原API的添加思維是相同的,只是Masonry語法更簡潔,代碼更優美
示例
需求:如下所示
實現如下:
UIView *redView = [[UIView alloc]init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
UIView *blueView = [[UIView alloc]init];
blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:blueView];
UIView *yellow = [[UIView alloc]init];
yellow.backgroundColor = [UIColor yellowColor];
[self.view addSubview:yellow];
UIView *green = [[UIView alloc]init];
green.backgroundColor = [UIColor greenColor];
[self.view addSubview:green];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_left).offset(0);// 使左邊等于self.view的控件,間隔為0;
make.top.equalTo(self.view.mas_top).offset(0); // 使頂部與self.view的控件的間隔為0;
make.width.equalTo(self.view.mas_width).multipliedBy(0.5); // 設置寬度為self.view的一半,multipliedBy是倍數的意思,也就是,使寬度等于self.view寬度的0.5倍
make.height.equalTo(self.view.mas_height).multipliedBy(0.5);// 設置高度為self.view的一半。
}];
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.and.height.equalTo(redView); // 跟redview等寬高
make.top.equalTo(redView.mas_top); // 與redview頂部對齊
make.leading.equalTo(redView.mas_right); // 與 redview的間隔為0
}];
[yellow mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.and.height.equalTo(redView); // 跟redview等寬高
make.top.equalTo(redView.mas_bottom); // 與 redview的間隔為0
make.leading.equalTo(redView); // 與redvie左對齊
}];
[green mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.and.height.equalTo(redView); // 跟redview等寬高
make.top.equalTo(yellow.mas_top); // 與yellow頂部對齊
make.leading.equalTo(yellow.mas_right); // 與 yellow的間隔為0
}];
PS:需要下載第三庫 Masonry ,并導入 #import "Masonry.h"
Masonry 下的 Autolayout Animation
UIView *redView = [[UIView alloc]init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
UIView *greenView = [[UIView alloc]init];
greenView.backgroundColor = [UIColor greenColor];
[self.view addSubview:greenView];
self.greenView = greenView;
UIView *blueView = [[UIView alloc]init];
blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:blueView];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_left).offset(20);
make.bottom.equalTo(self.view.mas_bottom).offset(-20);
make.width.equalTo(self.view.mas_width).multipliedBy(0.2);
make.height.equalTo(self.view.mas_height).multipliedBy(0.2);
}];
[greenView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(redView.mas_right).offset(20);
make.bottom.equalTo(self.view.mas_bottom).offset(-20);
make.width.equalTo(self.view.mas_width).multipliedBy(0.2);
make.height.equalTo(self.view.mas_height).multipliedBy(0.2);
}];
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(greenView.mas_right).offset(20);
make.bottom.equalTo(self.view.mas_bottom).offset(-20);
make.width.equalTo(self.view.mas_width).multipliedBy(0.2);
make.height.equalTo(self.view.mas_height).multipliedBy(0.2);
make.left.equalTo(redView.mas_right).offset(20).priority(250);
}];
讓我們最后加幾行代碼,來實現這個動畫吧!
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//先把綠色方塊從父視圖上移除
[self.greenView removeFromSuperview];
//動畫更新界面
[UIView animateWithDuration:1.0f animations:^{
[self.view layoutIfNeeded];
}];
}
運行效果
總結 Autolayout 使用
-
以后一律使用autolayout嗎?除了在storyboard中使用autolayout,代碼方式autolayout如何使用?
- 當需要展示的內容很多并且尺寸不固定;
- 程序需支持屏幕旋轉(主要是iPad程序,iPhone程序橫屏的場景有點非主流);
- 程序通用于iPhone和iPad;
-
使用autolayout 利弊
- 好處:可視化,實現簡單功能很節省時間
- 壞處:storyboard 使用 autolayout,移動一個控件就會讓弄亂那些約束;
-
autolayout有沒有局限性和解決不了的問題?兼容性怎么樣?效率怎么樣
- autolayout對view transforms支持的不好,這里有帖子詳細描述了這個問題
- 至于兼容性,只從iOS6就已經提出了autolayout的概念,大部分用戶應該是使用iOS7和iOS8系統,所以兼容性問題不會太大,但size class是iOS8才有的概念,所以還有有一定的適配工作量