iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

Hsusue發表於2018-10-28

前言

全面屏剛出時,網上有說反人類。但過去這麼久了,趨於技術的進步或看久了,大家也都慢慢習慣了(只是筆者還是買不起全面屏)。官方適配中文版文件也出來了。

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

圖源:( baijiahao.baidu.com/s?id=157902… )

回想起剛開始適配全面屏用了一種暴力、並不優雅的方法,以至於後來出了XS(MAX)和XR後出了bug。所以選擇一種可靠的、優雅的方案是很有必要的。如今網上關於探討適配全面屏的文章五花八門,筆者將探究其中的各種方案。

由於筆者水平有限,眼界狹窄,難免出現疏忽的地方,希望大神提出更好的方案。

全面屏的資料

  • 頂部
    iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

從以上兩圖,我們可以看出全面屏的頂部Statusbar變高了,其他部分沒變。

Largetitle是iOS11中新加入的特性。當然我們開發中很少用到Largetitle。

  • 底部

全面屏底部多出了高度為34的Home Indicator 區域。

個人總結的全面屏適配觀點

雖然筆者買不起XStyle,但是虛擬器應該能滿足適配的所有測試。所以開發中,請優先使用全面屏開發。筆者有個朋友,開發時用非全面屏,偶爾會出現忘記適配全面屏問題。如果用全面屏,開發效率將會進一步提升。畢竟介面適配全面屏的時候,很難忘記適配非全面屏。


App顯示介面大小是由App啟動頁決定的。

記得iPhoneX剛出時,App在其上面執行顯示居中,大小和6s一樣,上下各有一塊黑塊。嘗試列印出解析度驚奇發現不是官方宣傳的1124,2436。把啟動頁大小改了巨集才達到預想效果。如果用xib,那就沒什麼問題。啟動頁用圖片的話,要適配上@3x的圖片。


  1. 巨集怎麼定義

巨集裡只要能區分開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]
複製程式碼

最後說一句,利用巨集來寫雖然簡單,但有以下弊端。

  • 找不到能可靠性強的巨集XStyle。畢竟以後的手機解析度或者螢幕寬高肯定會變化(即使短時間內不會)。要適應新機型就要重新上線。
  • 假如以後新出的機劉海長度變了,到時又要修改巨集。
  • App適配起橫屏,也挺麻煩的。

即使有弊端,筆者還是覺得這麼寫可行。畢竟大多數App都不用支援橫屏,而且螢幕短時間內不會有太大變動。


人要保持一顆活到老學到老的心,這些弊端有方法避免。

iOS11出了安全區域SafeArea這個概念,用得好可以解決以上問題。要點時間適應。

弊端是目前大多App都支援iOS11.0-,這樣就要寫判斷版本號,程式碼多將近一倍。

優點也很明顯

  • 如果蘋果新出了新機型,不用改動程式碼適應的可能性非常大。這意味著不用為了適配問題上線新版本。

  • 橫屏時頂部不會有偏移。有橫屏需求的話,也許就不用為了橫屏做額外適配。

等以後App iOS11.0起步的時候(短時間不太可能qaq),個人感覺SafeArea將會成為主流。

但至少目前,看上去很美好,實際上適配寫多一倍的程式碼讓筆者望而生畏。


  1. SafeArea

iOS7以後,蘋果給UIViewController引入了topLayoutGuide 和 bottomLayoutGuide兩個屬性。用於表示頂部或底部的高度。根據有無顯示狀態列、導航欄、tabBar返回高度。如果VC內嵌VC,內嵌的VC將視為另起的頂底座標體系,不受原狀態列等影響。你可能聽都沒聽過這兩屬性,因為開發中我們習慣直接用巨集寫上數值,幾乎不用這兩個屬性。更何況那時iPhone還沒有全面屏這種東西。

到了iOS11,蘋果棄用了topLayoutGuide和bottomLayoutGuide兩個屬性。引入了safeArea代替。官方的建議是 不能被遮擋的內容和控制元件在安全區域範圍內顯示。如果檢視底部有按鈕,在全面屏下,請約束底部距離34,不要影響到Home功能。

  • safeAreaLayoutGuide

此屬性適用於自動佈局。

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

使用前

[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)

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

但橫屏時就不一樣了。巨集寫法會與上方有一段KStatusBarHeight距離,此方法沒有。這就是筆者說的其中一個優點,然而並沒有好到足夠讓筆者用它的程度。

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

最後有兩個觀點。

1⃣️對於在ViewController的view,推薦使用mas_safeAreaLayoutGuide。這樣就能動態更改,即使橫屏。

2⃣️對於在View之間的約束,推薦使用mas_left。一來沒必要用safeArea,二來不用判斷版本號,減少程式碼量。

  • safeAreaInsets

此屬性適用於手動計算frame。

X豎屏時控制器view的safeAreaInsets是(44,0,34,0);橫屏(0, 44, 21, 44)。

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

用到的是這個方法- (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;
    }
}
複製程式碼

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

用frame的話,不僅低於11的系統,就連高於11的系統,要適配起橫屏問題都比較麻煩。

所以筆者是趨向於用約束的,見仁見智吧。


這裡再貼出兩篇說SafeArea的文章。

筆者很久不用xib了,貼出現成的一篇文章。


  1. automaticallyAdjustsScrollViewInsets 和 contentInsetAdjustmentBehavior
  • 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;
}
複製程式碼

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

可以看到沒有改變tableView的frame,只是顯示範圍變了。

如果沒有這個屬性,我們要實現同樣的效果,tableView尺寸要這樣設定。當然手動修改insets也是可以的。

    x = 0, 
    y = KStatusBarAndNavigationBarHeight, 
    width = KScreenWidth, 
    height = KScreenHeight - KStatusBarAndNavigationBarHeight
複製程式碼

但是要注意:這種自動調整是在ScrollView是其根檢視新增的的第一個控制元件的時候,才會出現自動調整的效果。詳情檢視automaticallyAdjustsScrollViewInsets屬性

  • contentInsetAdjustmentBehavior

iOS11中廢棄了automaticallyAdjustsScrollViewInsets,取而代之的是contentInsetAdjustmentBehavior屬性。

該屬性原理和automaticallyAdjustsScrollViewInsets原理相似,是為了進一步適應安全區域。

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

如果你需要自定義內邊距,程式碼將變成以下這樣。

if (@available(iOS 11.0, *)) {
        self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
        self.automaticallyAdjustsScrollViewInsets = NO;
}
複製程式碼

contentInsetAdjustmentBehavior各個值之間的區別

連結裡有這觀點

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案


最後來談一下關於全面屏的適配方案。

  • 支援橫屏的App,每個介面帶ScrollView的吧。
  • 如果你的App最低支援iOS11.0,那麼用safeAreaLayoutGuide約束將非常刺激。橫屏基本不用管,除非排版需求不一樣。
  • 如果你的App要適配11.0-,不支援橫屏。那麼採用巨集來寫目前是主流。只是以後出了新型別可能要稍微修改一下巨集,並且執行逐個介面檢查。目前11.0起步的App畢竟少數,用safeAreaLayoutGuide反而要多判斷版本號。看以後App支援版本號趨勢吧。
  • 如果你的App要適配11.0-,支援橫屏。建議用約束。用frame可能寫死人,自求多福吧。
  • 橫屏有時實在沒辦法解決某些問題的話,就寫兩套。 關於iOS橫豎屏適配

電話、熱點狀態列問題

有電話打進來、手機開了熱點有連線,狀態列會變長20。雖然很多App並未對這些情況適配,但優秀的App應該要處理好。

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

橫屏和電話熱點並無直接關係。橫屏狀態下預設狀態列是不顯示的。

當狀態列增高時,App的控制器的view將會下移20,但是高度卻不變。tabBar不會有任何改變。所以如果某個介面scrollView一直到底的話,最好用約束到底部,這樣呼叫viewWillLayoutSubviews時就會修改scrollView高度。建議不要寫高度,不然會出現scrollView底部顯示丟失20高度的問題。 iOS 熱點 撥打電話 適配


個人總結的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]
複製程式碼
  1. UILabel的適配問題

相信開發過程中都遇到過字型適配問題。


UIFont

App如果要設定全域性字型,可以通過Swizzing修改。或者像以上巨集一樣,傳進引數,修改字型大小。

看過一篇文章,淘寶在Plus機型的字型都加大成1.5倍。筆者買不起Plus機型,更不敢裝淘寶這種剁手App,所以無法驗證。

iOS字型大小適配的幾種方法


本文要探究的是UILabel顯示的問題。重點並不在UIFont上。

先研究UILabel。

  • UILabel預設字型為[UIFont systemFontOfSize:17];// 每個中文文字寬度為17。但字母、數字寬度並非固定。例如1比0瘦。注意這將可能是個坑。 iOS--UILabel字型預設寬度和高度
  • numberOfLines只有設定了寬度約束的情況下起效。否則Label只會顯示一行。
  • UILabel預設垂直方向會居中顯示,即當Label高度高於text高度,文字在垂直方向上居中。水平方向上textAlignment可以設定。
    iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案
    如果要設定成頂部對齊,有好幾種方法。最粗暴的是用UITextView。 iOS開發技巧 - 使UILabel中的文字吸頂(頂部對齊)

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

  • lineBreakMode設定文字過長時省略號放哪。
label.lineBreakMode = NSLineBreakByCharWrapping;以字元為顯示單位顯

示,後面部分省略不顯示。

label.lineBreakMode = NSLineBreakByClipping;剪下與文字寬度相同的內

容長度,後半部分被刪除。

label.lineBreakMode = NSLineBreakByTruncatingHead;前面部分文字

以……方式省略,顯示尾部文字內容。

label.lineBreakMode = NSLineBreakByTruncatingMiddle;中間的內容

以……方式省略,顯示頭尾的文字內容。

label.lineBreakMode = NSLineBreakByTruncatingTail;結尾部分的內容

以……方式省略,顯示頭的文字內容。

label.lineBreakMode = NSLineBreakByWordWrapping;以單詞為顯示單位顯

示,後面部分省略不顯示。
複製程式碼
  • adjustsFontSizeToFitWidth //設定字型大小適應label寬度
  • minimumScaleFactor

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

  • sizeToFit

改變Label的尺寸以顯示文字。需要注意的是,需要在label.text賦值後執行。如果寬高都進行了約束,那麼呼叫sizeToFit將無效果。如果只約束了寬度,並且行數非1,那麼sizeToFit會修改Label的高度;如果只約束了高度,或者行數為1,那麼sizeToFit只會修改Label的寬度。如果二者皆未約束,只會修改Label寬度。

  • sizeToFitadjustsFontSizeToFitWidth的區別。

從字面上我們就能區分開,前者是改變Label的寬高,後者是改變字型大小。

反正以後做專案的時候,明確需求,我們是固定了字型的大小來適配label的寬,還是固定了label的寬來適配字型的大小,前者用sizeToFit,後者用adjustFontsToFit。


以淘寶舉例。為了研究只能下這剁手App了。

se下搜iPhoneX

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

6s下搜iPhoneX

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

這裡無視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,大多數時候都會頂部對齊、換行、裁剪,或設定自動高度。


  1. UI在不同尺寸的適配

來說最後一個非常有用的巨集。

#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中更顯神威。

iOS 關於全面屏適配的方案及UI在不同尺寸下適配方案

設計圖算好了兩個cell的間距,每個cell的大小,整個collectionView的大小,contentInset。這時我們採用這個巨集,在不同螢幕下的適配問題將迎刃而解。

這裡為什麼不寫出KScaleHeight呢?

筆者並不是說不能用,只是view通常是被寬度所限制。你見過微信的cell文字內容高度有變化嗎hhhhh。就像圖片尺寸變了,高度也是被圖片寬度帶動,而不是螢幕高度。

當然此巨集雖很有用,但是開發中還是要經過考慮哪些地方需要用。


參考

相關文章