全面屏剛出時,網上有說反人類。但過去這麼久了,趨於技術的進步或看久了,你們也都慢慢習慣了(只是筆者仍是買不起全面屏)。官方適配中文版文檔也出來了。html
圖源:( baijiahao.baidu.com/s?id=157902… )ios
回想起剛開始適配全面屏用了一種暴力、並不優雅的方法,以致於後來出了XS(MAX)和XR後出了bug。因此選擇一種可靠的、優雅的方案是頗有必要的。現在網上關於探討適配全面屏的文章五花八門,筆者將探究其中的各類方案。安全
因爲筆者水平有限,眼界狹窄,不免出現疏忽的地方,但願大神提出更好的方案。bash
從以上兩圖,咱們能夠看出全面屏的頂部Statusbar
變高了,其餘部分沒變。微信
Largetitle
是iOS11中新加入的特性。固然咱們開發中不多用到Largetitle。app
全面屏底部多出了高度爲34的Home Indicator 區域。iphone
雖然筆者買不起XStyle,可是虛擬器應該能知足適配的全部測試。因此開發中,請優先使用全面屏開發。筆者有個朋友,開發時用非全面屏,偶爾會出現忘記適配全面屏問題。若是用全面屏,開發效率將會進一步提高。畢竟界面適配全面屏的時候,很難忘記適配非全面屏。ide
App顯示界面大小是由App啓動頁決定的。佈局
記得iPhoneX剛出時,App在其上面運行顯示居中,大小和6s同樣,上下各有一塊黑塊。嘗試打印出分辨率驚奇發現不是官方宣傳的1124,2436。把啓動頁大小改了宏才達到預想效果。若是用xib,那就沒什麼問題。啓動頁用圖片的話,要適配上@3x的圖片。測試
宏裏只要能區分開XStyle,其餘高度就好說。
網上不少教程都是按照分辨率來區分。然而,根據上面咱們能夠發現,XStyle的分辨率並不是固定。因此單純按照分辨率是不行的。筆者一開始適配X就是這樣,後來XSMax出了問題,被迫強行更新XCode10用XSMax,發現宏沒寫好(順便吐槽一句,XCode10真是噩夢,又懶得下回去)。
也有舊教程是按照屏幕寬高。但根據上面數據也不是固定的,因此要注意。這兩種失效宏都列在下面。
// 單純根據分辨率
#define K_iPhoneXStyle ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO)
// 單純根據屏幕寬高
#define K_iPhoneXStyle (KScreenWidth == 375.f && KScreenHeight == 812.f ? YES : NO)
複製代碼
其實筆者很想弄出一個通用的宏,不那麼怕出新機會失效的宏,但奈何實在想不到。只能照XStyle不同的數據寫出宏。下面列出的宏在XS Max時仍是有效的。至於之後就說不許了,各位仍是要根據新機分辨率或者寬高適當修改。
#define K_iPhoneXStyle ( (CGSizeEqualToSize(CGSizeMake(414, 896), [[UIScreen mainScreen] bounds].size)) || ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO) )
複製代碼
或者
#define K_iPhoneXStyle ((KScreenWidth == 375.f && KScreenHeight == 812.f ? YES : NO) || (KScreenWidth == 414.f && KScreenHeight == 896.f ? YES : NO))
複製代碼
還有其餘的宏
#define KScreenWidth ([UIScreen mainScreen].bounds.size.width)
#define KScreenHeight ([UIScreen mainScreen].bounds.size.height)
#define K_iPhoneXStyle ((KScreenWidth == 375.f && KScreenHeight == 812.f ? YES : NO) || (KScreenWidth == 414.f && KScreenHeight == 896.f ? YES : NO))
#define KStatusBarAndNavigationBarHeight (K_iPhoneXStyle ? 88.f : 64.f)
#define KStatusBarHeight (K_iPhoneXStyle ? 44.f : 20.f)
#define KTabbarHeight (K_iPhoneXStyle ? 83.f : 49.f)
#define KMagrinBottom (K_iPhoneXStyle ? 34.f : 0.f)
複製代碼
還有些宏,是適配字體或者view用的。將在後面介紹UI在不一樣尺寸下適配方案再提起。
#define KScaleWidth(width) ((width)*(KScreenWidth/375.f))
#define IsIphone6P SCREEN_WIDTH==414
#define SizeScale (IsIphone6P ? 1.5 : 1)
#define kFontSize(value) value*SizeScale
#define kFont(value) [UIFont systemFontOfSize:value*SizeScale]
複製代碼
最後說一句,利用宏來寫雖然簡單,但有如下弊端。
即便有弊端,筆者仍是以爲這麼寫可行。畢竟大多數App都不用支持橫屏,並且屏幕短期內不會有太大變更。
人要保持一顆活到老學到老的心,這些弊端有方法避免。
iOS11出了安全區域SafeArea這個概念,用得好能夠解決以上問題。要點時間適應。
弊端是目前大多App都支持iOS11.0-,這樣就要寫判斷版本號,代碼多將近一倍。
優勢也很明顯
若是蘋果新出了新機型,不用改動代碼適應的可能性很是大。這意味着不用爲了適配問題上線新版本。
橫屏時頂部不會有偏移。有橫屏需求的話,也許就不用爲了橫屏作額外適配。
等之後App iOS11.0起步的時候(短期不太可能qaq),我的感受SafeArea將會成爲主流。
但至少目前,看上去很美好,實際上適配寫多一倍的代碼讓筆者望而生畏。
iOS7之後,蘋果給UIViewController
引入了topLayoutGuide 和 bottomLayoutGuide兩個屬性。用於表示頂部或底部的高度。根據有無顯示狀態欄、導航欄、tabBar返回高度。若是VC內嵌VC,內嵌的VC將視爲另起的頂底座標體系,不受原狀態欄等影響。你可能聽都沒聽過這兩屬性,由於開發中咱們習慣直接用宏寫上數值,幾乎不用這兩個屬性。更況且那時iPhone尚未全面屏這種東西。
到了iOS11,蘋果棄用了topLayoutGuide和bottomLayoutGuide兩個屬性。引入了safeArea代替。官方的建議是 不能被遮擋的內容和控件在安全區域範圍內顯示。若是視圖底部有按鈕,在全面屏下,請約束底部距離34,不要影響到Home功能。
此屬性適用於自動佈局。
使用前
[NSLayoutConstraint constraintWithItem:someView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
複製代碼
使用後
[NSLayoutConstraint constraintWithItem:someView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view.safeAreaLayoutGuide attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
複製代碼
筆者用的是Masonry。注意該屬性是iOS11後出現的。由於X發佈時,最低版本超過了11,因此全面屏都能用此屬性。在這裏能夠看出,由於11不支持,這代碼多了一倍。咱們徹底能夠不用這新屬性,減小一半代碼爽歪歪。因此筆者目前開發還未使用。
[testView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(@44);
if (@available(iOS 11.0,*)) {
make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
make.left.equalTo(self.view.mas_safeAreaLayoutGuideLeft);
make.right.equalTo(self.view.mas_safeAreaLayoutGuideRight);
} else {
make.top.equalTo(self.view).offset(KStatusBarHeight);
make.left.equalTo(self.view);
make.right.equalTo(self.view);
}
}];
複製代碼
筆者沒有全面屏,只能展現一下熱點了。該效果與用宏KStatusBarHeight
同樣。
(注:此手機系統12.0)
但橫屏時就不同了。宏寫法會與上方有一段KStatusBarHeight距離,此方法沒有。這就是筆者說的其中一個優勢,然而並無好到足夠讓筆者用它的程度。
最後有兩個觀點。
1⃣️對於在ViewController的view,推薦使用mas_safeAreaLayoutGuide。這樣就能動態更改,即便橫屏。
2⃣️對於在View之間的約束,推薦使用mas_left。一來不必用safeArea,二來不用判斷版本號,減小代碼量。
此屬性適用於手動計算frame。
X豎屏時控制器view的safeAreaInsets是(44,0,34,0);橫屏(0, 44, 21, 44)。
用到的是這個方法- (void)viewSafeAreaInsetsDidChange;
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
UIView *testView = [[UIView alloc] initWithFrame:CGRectMake(0, KStatusBarHeight, KScreenWidth, 200)];
testView.backgroundColor = [UIColor blackColor];
[self.view addSubview:testView];
self.testView = testView;
}
- (void)viewSafeAreaInsetsDidChange {
[super viewSafeAreaInsetsDidChange];
NSLog(@"%s", __func__);
[self updateFrame];
}
- (void)updateFrame {
if (@available(iOS 11.0, *)) {
CGRect newFrame = self.testView.frame;
newFrame.origin.x = self.view.safeAreaInsets.left;
newFrame.size.width = KScreenWidth - self.view.safeAreaInsets.left - self.view.safeAreaInsets.right;
self.testView.frame = newFrame;
}
}
複製代碼
用frame的話,不只低於11的系統,就連高於11的系統,要適配起橫屏問題都比較麻煩。
因此筆者是趨向於用約束的,見仁見智吧。
這裏再貼出兩篇說SafeArea的文章。
筆者好久不用xib了,貼出現成的一篇文章。
automaticallyAdjustsScrollViewInsets:
在iOS7.0之後,相對於ScrollView新增屬性,默認爲YES,系統會根據所在界面的astatus bar, search bar, navigation bar, toolbar, or tab bar等自動調整ScrollView的inset。
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"我是導航條";
self.view.backgroundColor = [UIColor redColor];
UITableView *testTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight) style:UITableViewStylePlain];
testTableView.backgroundColor = [UIColor blueColor];
testTableView.delegate = self;
testTableView.dataSource = self;
[self.view addSubview:testTableView];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
cell.textLabel.text = [NSString stringWithFormat:@"%ld", (long)indexPath.row];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 20;
}
複製代碼
能夠看到沒有改變tableView的frame,只是顯示範圍變了。
若是沒有這個屬性,咱們要實現一樣的效果,tableView尺寸要這樣設置。固然手動修改insets也是能夠的。
x = 0,
y = KStatusBarAndNavigationBarHeight,
width = KScreenWidth,
height = KScreenHeight - KStatusBarAndNavigationBarHeight
複製代碼
可是要注意:這種自動調整是在ScrollView是其根視圖添加的的第一個控件的時候,纔會出現自動調整的效果。詳情查看automaticallyAdjustsScrollViewInsets屬性
iOS11中廢棄了automaticallyAdjustsScrollViewInsets,取而代之的是contentInsetAdjustmentBehavior屬性。
該屬性原理和automaticallyAdjustsScrollViewInsets原理類似,是爲了進一步適應安全區域。
若是你須要自定義內邊距,代碼將變成如下這樣。
if (@available(iOS 11.0, *)) {
self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
self.automaticallyAdjustsScrollViewInsets = NO;
}
複製代碼
contentInsetAdjustmentBehavior各個值之間的區別
連接裏有這觀點
最後來談一下關於全面屏的適配方案。
safeAreaLayoutGuide
約束將很是刺激。橫屏基本不用管,除非排版需求不同。safeAreaLayoutGuide
反而要多判斷版本號。看之後App支持版本號趨勢吧。有電話打進來、手機開了熱點有鏈接,狀態欄會變長20。雖然不少App並未對這些狀況適配,但優秀的App應該要處理好。
橫屏和電話熱點並沒有直接關係。橫屏狀態下默認狀態欄是不顯示的。
當狀態欄增高時,App的控制器的view將會下移20,可是高度卻不變。tabBar不會有任何改變。因此若是某個界面scrollView一直到底的話,最好用約束到底部,這樣調用viewWillLayoutSubviews時就會修改scrollView高度。建議不要寫高度,否則會出現scrollView底部顯示丟失20高度的問題。 iOS 熱點 撥打電話 適配
這時就要說回上面提到的宏了。
#define KScaleWidth(width) ((width)*(KScreenWidth/375.f))
#define IsIphone6P SCREEN_WIDTH==414
#define SizeScale (IsIphone6P ? 1.5 : 1)
#define kFontSize(value) value*SizeScale
#define kFont(value) [UIFont systemFontOfSize:value*SizeScale]
複製代碼
相信開發過程當中都遇到過字體適配問題。
UIFont
App若是要設置全局字體,能夠經過Swizzing修改。或者像以上宏同樣,傳進參數,修改字體大小。
看過一篇文章,淘寶在Plus機型的字體都加大成1.5倍。筆者買不起Plus機型,更不敢裝淘寶這種剁手App,因此沒法驗證。
本文要探究的是UILabel顯示的問題。重點並不在UIFont上。
先研究UILabel。
numberOfLines
只有設定了寬度約束的狀況下起效。不然Label只會顯示一行。textAlignment
能夠設置。
若是要設置成頂部對齊,有好幾種方法。最粗暴的是用UITextView。 iOS開發技巧 - 使UILabel中的文字吸頂(頂部對齊)lineBreakMode
設置文字過長時省略號放哪。label.lineBreakMode = NSLineBreakByCharWrapping;以字符爲顯示單位顯
示,後面部分省略不顯示。
label.lineBreakMode = NSLineBreakByClipping;剪切與文本寬度相同的內
容長度,後半部分被刪除。
label.lineBreakMode = NSLineBreakByTruncatingHead;前面部分文字
以……方式省略,顯示尾部文字內容。
label.lineBreakMode = NSLineBreakByTruncatingMiddle;中間的內容
以……方式省略,顯示頭尾的文字內容。
label.lineBreakMode = NSLineBreakByTruncatingTail;結尾部分的內容
以……方式省略,顯示頭的文字內容。
label.lineBreakMode = NSLineBreakByWordWrapping;以單詞爲顯示單位顯
示,後面部分省略不顯示。
複製代碼
adjustsFontSizeToFitWidth
//設置字體大小適應label寬度minimumScaleFactor
sizeToFit
改變Label的尺寸以顯示文字。須要注意的是,須要在label.text賦值後執行。若是寬高都進行了約束,那麼調用sizeToFit將無效果。若是隻約束了寬度,而且行數非1,那麼sizeToFit會修改Label的高度;若是隻約束了高度,或者行數爲1,那麼sizeToFit只會修改Label的寬度。若是兩者皆未約束,只會修改Label寬度。
sizeToFit
和adjustsFontSizeToFitWidth
的區別。從字面上咱們就能區分開,前者是改變Label的寬高,後者是改變字體大小。
反正之後作項目的時候,明確需求,咱們是固定了字體的大小來適配label的寬,仍是固定了label的寬來適配字體的大小,前者用sizeToFit,後者用adjustFontsToFit。
以淘寶舉例。爲了研究只能下這剁手App了。
se下搜iPhoneX
6s下搜iPhoneX
這裏無視Label左邊的圖標(天貓、雙11)。
其商品標題有兩行。以前提到了,若是不做處理,Label默認垂直方向居中。若是文字長度只有一行,那會顯示奇怪。因此筆者來約束的話,先作垂直方向處理,而且約束了寬度距離左邊圖片,距離cell.contentView右邊。而後設置行數爲2(或者約束高度,行數爲0,lineBreakMode裁剪)。
還有一種方案是Label根據文字長度自動適配高度,並設置最大高度限制。輸入框高度就用相似方案。網上搜UITextView自動高度一堆教程。筆者懶得翻就不弄了。基本原理是根據文字大小長度、Label的寬度、文字間距,算出文字高度,而後設置Label高度爲 最大限制高度與文字高度 較小者。
再來看價格和付款人數Label。
筆者設計的話,會採用如下形式。
priceLabel.font = ...
[priceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(imageView.mas_right).offset(...);
make.height.equalTo(...);
make.top...;
}];
numberOfPeopleLabel.font = ...
[numberOfPeopleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(priceLabel.mas_right).offset(...);
make.height.equalTo(...);
make.top...;
}];
複製代碼
而後在cell設置model時
priceLabel.text = ...
[priceLabel sizeToFit];
numberOfPeopleLabel.text = ...
[numberOfPeopleLabel sizeToFit];
複製代碼
總結一下,開發中不多會用到adjustsFontSizeToFitWidth
,大多數時候都會頂部對齊、換行、裁剪,或設置自動高度。
來講最後一個很是有用的宏。
#define KScaleWidth(width) ((width)*(KScreenWidth/375.f))
複製代碼
筆者遇到的設計師給的圖都是i6屏幕,其寬度爲375.f。若是給的圖不是,那麼將這個宏數值修改便可。
這個宏有什麼用呢?
其實就是一個比例轉換的問題。不一樣屏幕下,某些UI可能大小不同,這時候採用這個宏將會很是方便。
仍是舉回上面的兩張淘寶圖例子。
筆者目測(目測而已),cell是不一樣高度的。假如6s中商品圖的寬度150.f,佔屏寬0.4。在se中按照比例,320 * 0.4,爲128。
那麼咱們用這個宏,就能一步到位。KScaleWidth(150),在6s中就爲150,在se中爲128。
除此以外,間距約束用這個宏也有奇效。
這個宏在collectionView中更顯神威。
設計圖算好了兩個cell的間距,每一個cell的大小,整個collectionView的大小,contentInset。這時咱們採用這個宏,在不一樣屏幕下的適配問題將迎刃而解。
這裏爲何不寫出KScaleHeight呢?
筆者並非說不能用,只是view一般是被寬度所限制。你見過微信的cell文本內容高度有變化嗎hhhhh。就像圖片尺寸變了,高度也是被圖片寬度帶動,而不是屏幕高度。
固然此宏雖頗有用,可是開發中仍是要通過考慮哪些地方須要用。