iOS多裝置適配簡史以及相應的API支撐實現

歐陽大哥2013發表於2019-01-31

遠古的iPhone3和iPhone4時代,裝置尺寸都是固定3.5inch,沒有所謂的適配的問題,只需要用檢視的frame屬性進行硬編碼即可。隨著時間的推移,蘋果的裝置種類越來越多,尺寸也越來越大,單純的frame已經不能簡單解決問題了,於是推出了AutoLayout技術和SizeClasses技術來解決多種裝置的適配問題。一直在做iOS開發的程式設計師相信在下面的兩個版本交界處需要處理適配的坎一定讓你焦頭爛額過:

  1. iOS7出來後檢視控制器的根檢視預設的尺寸是佔據整個螢幕的,如果有半透明導航條的話也預設是延伸到導航欄和狀態列的下面。這段時間相信你對要同時滿足iOS7和以下的版本進行大面積的改版和特殊適配處理,尤其是狀態列的高度問題尤為棘手。

  2. iOS11出來後尤其是iPhoneX裝置推出,iPhoneX裝置的特殊性表現為頂部的狀態列高度由20變為了44,底部還出現了一個34的安全區,當橫屏時還需要考慮左右兩邊的44的縮排處理。你需要對所有的佈局程式碼進行重新適配和梳理以便相容iPhoneX和其他裝置,這裡面還是狀態列的高度以及底部安全區的的高度尤為棘手。

個人認為這兩個版本的釋出是iOS開發人員遇到的需要大量佈局改版的版本。為了達到完美適配我們可能需要寫大量的if,else以及寫很多巨集以及版本相容來進行特殊處理。當然蘋果也為上面兩次大改版提供了諸多的解決方案:

  1. iOS7中對檢視控制器提供瞭如下屬性來解決版本相容性的問題:
@property(nonatomic,assign) UIRectEdge edgesForExtendedLayout NS_AVAILABLE_IOS(7_0); // Defaults to UIRectEdgeAll
@property(nonatomic,assign) BOOL extendedLayoutIncludesOpaqueBars NS_AVAILABLE_IOS(7_0); // Defaults to NO, but bars are translucent by default on 7_0.  
@property(nonatomic,assign) BOOL automaticallyAdjustsScrollViewInsets API_DEPRECATED_WITH_REPLACEMENT("Use UIScrollView's contentInsetAdjustmentBehavior instead", ios(7.0,11.0),tvos(7.0,11.0)); // Defaults to YES

@property(nonatomic,readonly,strong) id<UILayoutSupport> topLayoutGuide API_DEPRECATED_WITH_REPLACEMENT("-[UIView safeAreaLayoutGuide]", ios(7.0,11.0), tvos(7.0,11.0));
@property(nonatomic,readonly,strong) id<UILayoutSupport> bottomLayoutGuide API_DEPRECATED_WITH_REPLACEMENT("-[UIView safeAreaLayoutGuide]", ios(7.0,11.0), tvos(7.0,11.0));

複製程式碼
  1. iOS11中提出了一個安全區的概念,要求我們的可操作檢視都放置在安全區內,並對檢視和滾動檢視提供瞭如下擴充套件屬性:
@property (nonatomic,readonly) UIEdgeInsets safeAreaInsets API_AVAILABLE(ios(11.0),tvos(11.0));
- (void)safeAreaInsetsDidChange API_AVAILABLE(ios(11.0),tvos(11.0));

/* The top of the safeAreaLayoutGuide indicates the unobscured top edge of the view (e.g, not behind
 the status bar or navigation bar, if present). Similarly for the other edges.
 */
@property(nonatomic,readonly,strong) UILayoutGuide *safeAreaLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));
複製程式碼
/* When contentInsetAdjustmentBehavior allows, UIScrollView may incorporate
 its safeAreaInsets into the adjustedContentInset.
 */
@property(nonatomic, readonly) UIEdgeInsets adjustedContentInset API_AVAILABLE(ios(11.0),tvos(11.0));

/* Also see -scrollViewDidChangeAdjustedContentInset: in the UIScrollViewDelegate protocol.
 */
- (void)adjustedContentInsetDidChange API_AVAILABLE(ios(11.0),tvos(11.0)) NS_REQUIRES_SUPER;

/* Configure the behavior of adjustedContentInset.
 Default is UIScrollViewContentInsetAdjustmentAutomatic.
 */
@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior API_AVAILABLE(ios(11.0),tvos(11.0));

/* contentLayoutGuide anchors (e.g., contentLayoutGuide.centerXAnchor, etc.) refer to
 the untranslated content area of the scroll view.
 */
@property(nonatomic,readonly,strong) UILayoutGuide *contentLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));

/* frameLayoutGuide anchors (e.g., frameLayoutGuide.centerXAnchor) refer to
 the untransformed frame of the scroll view.
 */
@property(nonatomic,readonly,strong) UILayoutGuide *frameLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));
複製程式碼

這些屬性的具體意義這裡就不多說了,網路上以及蘋果的官方都有很多資料在介紹這些屬性的意思。從上面的這些屬性中可以看出蘋果提出的這些解決方案其主要是圍繞解決檢視和導航條、滾動檢視、狀態列、螢幕邊緣之間的關係而進行的。因為iOS7和iOS11兩個版本中控制器中的檢視和上面所列出的一些內容之間的關係變化最大。

NSLayoutConstraint約束以及iOS9上的封裝改進

在iOS6時代蘋果推出了AutoLayout的技術解決方案,這是一套採用以相對約束來替代硬編碼的解決方法,然而糟糕的方法名和使用方式導致使用成本和程式碼量的急劇增加。比如下面的一段程式碼:

    UIButton *button = [self createDemoButton:NSLocalizedString(@"Pop layoutview at center", "") action:@selector(handleDemo1:)];
    button.translatesAutoresizingMaskIntoConstraints = NO;  //button使用AutoLayout
    [scrollView addSubview:button];

    //下面的程式碼是iOS6以來自帶的約束佈局寫法,可以看出程式碼量較大。
    [scrollView addConstraint:[NSLayoutConstraint  constraintWithItem:button attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
    
    [scrollView addConstraint:[NSLayoutConstraint  constraintWithItem:button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeTop multiplier:1 constant:10]];
    
    [scrollView addConstraint:[NSLayoutConstraint  constraintWithItem:button attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:40]];
    
    [scrollView addConstraint:[NSLayoutConstraint  constraintWithItem:button attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeWidth multiplier:1 constant:-20]];
複製程式碼

一個簡單的將按鈕放到一個UIScrollView中去的程式碼,當用AutoLayout來實現時出現了程式碼量風暴問題。對於約束的設定到了iOS9以後有了很大的改進,蘋果對約束的設定進行了封裝,提供了三個類:NSLayoutXAxisAnchor, NSLayoutYAxisAnchor, NSLayoutDimension來簡化約束的設定,還是同樣的功能用新的類來寫約束就簡潔清晰很多了:

    UIButton *button = [self createDemoButton:NSLocalizedString(@"Pop layoutview at center", "") action:@selector(handleDemo1:)];
    button.translatesAutoresizingMaskIntoConstraints = NO;  //button使用AutoLayout
    [scrollView addSubview:button];
    [button.centerXAnchor constraintEqualToAnchor:scrollView.centerXAnchor].active = YES;
    [button.topAnchor constraintEqualToAnchor:scrollView.topAnchor constant:10].active = YES;
    [button.heightAnchor constraintEqualToConstant:40].active = YES;
    [button.widthAnchor constraintEqualToAnchor:scrollView.widthAnchor multiplier:1 constant:-20].active = YES;
複製程式碼
UIStackView

在iOS9中還提供了一個UIStackView的類來簡化那些檢視需要從上往下或者從左往右依次新增排列的場景,通過UIStackView容器檢視的使用就不再需要為每個子檢視新增冗餘的依賴約束關係了。在大量的實踐中很多應用的各板塊其實都是按順序從上到下排列或者從左到右排列的。所以如果您的應用最低支援到iOS9的話就可以大量的應用這個類來構建你的程式了。

佔位檢視類UILayoutGuide

在iOS9以前兩個檢視之間的間距和間隔是無法支援浮動和可伸縮設定的,以及我們可以需要在兩個檢視之間保留一個浮動尺寸的空白區域,解決的方法是在它們中間加入一個透明顏色的UIView來進行處理,不管如何只要是View都需要進行渲染和繪製從而有可能一定程度上影響程式的效能,而在iOS9以後提供了一個佔位檢視類UILayoutGuide,這個類就像是一個普通的檢視一樣可以為它設定約束,也可以將它新增進入檢視中去,也可以將這個佔位檢視作為其他檢視的約束依賴項,唯一的不同就是佔位檢視不會進行任何的渲染和繪製,它只會參與佈局處理。因此這個類的引入可以很大程度上解決那些浮動間距的問題。

SizeClasses多螢幕適配

當我們的程式可能需要同時在橫屏和豎屏下執行並且橫屏和豎屏下的佈局還不一致時,而且希望我們的應用在小螢幕上和大螢幕上(比如iPhone8 Plus 以及iPhoneX S Max)的佈局有差異時,我們可能需要用到蘋果的SizeClasses技術。這是蘋果在iOS8中推出來的一個概念。 但是在實際的實踐中我們很少有看到使用SizeClasses的例子和場景以及在我們開發中很少有使用到這方面的技術,所以我認為這應該是蘋果的一個多螢幕適配的失敗解決的方案。從字面理解SizeClasses就是尺寸的種類,蘋果將裝置的寬和高分為了壓縮和常規兩種尺寸型別,因此我們可以得到如下幾種型別的裝置:

裝置 方向 型別
iPhone4/5/6/7/X 豎屏 w:Compact h:Regular
iPhone4/5/6/7/X 橫屏 w:Compact h:Compact
iPhone6/7Plus, iPhoneXMax 豎屏 w:Compact h:Regular
iPhone6/7Plus, iPhoneXMax 橫屏 w:Regular h:Compact
所有iPad 豎屏 w:Regular h: Regular
所有iPad 橫屏 w:Regular h: Regular
所有iWatch 豎屏 w: Compact h: Compact
所有iWatch 橫屏 w: Compact h: Compact

很欣慰的是如果您的應用是一個帶有系統導航條的應用時很多適配的問題都能夠得到很好的解決,因為系統已經為你做了很多事情,你不需要做任何特殊的處理。而如果你的應用的某個介面是present出來的,或者是你自己實現的自定義導航條的話,那麼你可能就需要自己來處理各種版本的適配問題了。並且如果你的應用可能還有橫豎屏的話那這個問題就更加複雜了。

最後除了可以用系統提供的API來解決所有的適配問題外,還向大家推薦我的開源佈局庫:MyLayout。它同時支援Objective-C以及Swift版本。而且用這個庫後上面的所有適配問題都不是問題。


歡迎大家訪問歐陽大哥2013的github地址

相關文章