iOS11適配詳解

小火爐發表於2017-10-31

題外話

這是一次公司內部技術分享會的內容,內容共分為三個部分:

  • Xcode9新特性
  • iOS 11 適配
  • iPhone X適配
    這是第二部分,如有需要請持續關注。
    第一部分Xcode9新特性
    言歸正傳

掀起江湖恩怨

iOS 11正式版已經來了,作為一個iOS開發者,這意味著沒有適配iOS 11都晚了。好在還在Beta階段我司技術大牛達叔第一時間體驗了一把,並仔細的跑了一遍播放端APP觸手TV和錄製端APP觸手錄,除了有一個由第三方庫WebViewJavascriptBridgeBase引起的嚴重crash,兩個APP在iOS 11下基本沒什麼問題,發現的問題已經被達叔提前fixed了。所以組裡一直沒有進行適配工作,而是把精力放在了最近的大版本開發上。觀察發現,直接從AppStore下載的應用,在iOS 11上跑起來是沒有什麼問題的,如果使用Xcode 9 Building後在執行,就或多或少的出現問題。因為Xcode 9 的Base SDKS是基於iOS 11的。所以還是需要進行適配的。

風雲再起

通過閱覽網上適配iOS 11的同行案列,結合觸手TVAPP實際問題,經過彙總,以下可能是需要適配的點。找到問題的根源,才能幫助我們解決問題。
為什麼會出現上述問題呢?我們看看iOS 11有些什麼新增改動。
這裡只列出部分以及跟今天主題相關的部分。詳情可以看看官網what's new in iOS 11

UIViewController 與 UIView
主要變化部分:
  • UIViewController廢棄LayoutGuide。iOS7之後,為了輔助Autolayout佈局系統,Apple新增UILayoutSupport協議。就是被很多人忽略的topLayoutGuidebottomLayoutGuide
@interface UIViewController (UILayoutSupport)
// These objects may be used as layout items in the NSLayoutConstraint API
@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));
@property(nonatomic) UIEdgeInsets additionalSafeAreaInsets API_AVAILABLE(ios(11.0), tvos(11.0));複製程式碼

這個兩個屬性是readonly的,一般我們也用不到,主要作用是輔助Controller的View在佈局時知道從哪裡開始佈局,到什麼地方結束佈局。在使用SB或者XIB檔案佈局的時候,可以進行設定,確定開始佈局和結束佈局的參考點。在iOS 11 已經標記API_DEPRECATED_WITH_REPLACEMENT。取而代之的是UIView新的APIsafeAreaInsets

LayoutGuide
LayoutGuide

  • UIView增加safeAreaInsets:UIEdgeInsets安全區概念。用於替代輔助自動佈局的LayoutGuide。安全區域定義了佈局View除各種Bar後剩下的可見區域。此屬性是readonly的,想要改動,需要操作UIViewControlleradditionalSafeAreaInsets屬性。

    簡單理解就是去除系統的各種Bar以及左右margin(如果有設定過) 後的可用佈局區域。

    圖中淡藍色區域即為安全區域:

    safeAreaInsets
    safeAreaInsets

    如果在View上的控制元件沒有被遮擋,safeAreaInsets = {(0,0),(0,0)}分別對應於(top,left,bottom,right)
    假設子ViewA頂部被navigationBar遮擋20pt,相應safeAreaInsets = {(20,0),(0,0)}

    注意:如果採用SB、XIB佈局UI的,safeAreaInsets最低支援版本為iOS 9.0。考慮到相容性,觸手TV是不能使用安全區域的。
  • UIViewController廢棄automaticallyAdjustsScrollViewInsetsAPI。這是個bool值屬性,描述了是否自動適配scrollView的contentInsets。值為true時,scrollView的內容不會被系統可見的各種bar所遮擋(statusbarnavigationBartabBartoolBar),一般會引起tableViewcollectionViewscrollView的content下移bar的高度值個畫素。如果不需要自動調整,將值設為false

  • UIViewController新增修改關聯View安全區域的APIadditionalSafeAreaInsets。可對安全區域的值進行增減,滿足自定義UI的需求。並提供方法viewSafeAreaInsetsDidChange,用於在安全區域改變後,進行佈局的調整。

  • 相應的,UIScrollView增加列舉屬性contentInsetAdjustmentBehavior,描述scrollView如何調整contentInset(實際是調整adjustedContentInset屬性),配合安全區域使用。最後UIscrollView的contentInset值為adjustedContentInsetsafeAreaInsets之和。

    有4個可選值:

      typedef NS_ENUM(NSInteger, UIScrollViewContentInsetAdjustmentBehavior) {
              UIScrollViewContentInsetAdjustmentAutomatic, //與 automaticallyAdjustsScrollViewInsets 類似
              UIScrollViewContentInsetAdjustmentScrollableAxes, // 在滾動的當前軸向上進行自動調整
              UIScrollViewContentInsetAdjustmentNever,  //不進行任何的調整
              UIScrollViewContentInsetAdjustmentAlways, // contentInset等於View的safeAreaInsets
          } API_AVAILABLE(ios(11.0),tvos(11.0));複製程式碼

    UIScrollViewDelegate新增scrollViewDidChangeAdjustedContentInset方法,當adjustedContentInset改變後通知使用者進行佈局調整。

總結:

  • 如果使用SB、XIB佈局時啟用了安全區域,IB會參考安全區域進行佈局。
  • 佈局時沒有以安全區域作為參照時,直接設定安全區域,系統並不會對佈局做出自適應動作,可以通過安全區域改變回撥方法進行調整。
  • 如果是UIscrollView或子類,如果設定了UIScrollViewContentInsetAdjustmentBehavior不等於UIScrollViewContentInsetAdjustmentNever,系統會自動適配UIScrollView的內容都在安全區域內,保證內容不被各種Bar遮住。
NavigationBar的改變

NavigationBar 多了個contentView,這個View在開啟了大標題時,上面會有個titleLable。
NavigationBartitleView支援自動佈局。需要使用者自動撐開新增到上面的View。

定位許可權

在iOS11,原有的NSLocationAlwaysUsageDeion被降級為NSLocationWhenInUseUsageDeion
.plist沒檔案中配置NSLocationAlwaysAndWhenInUseUsageDeion,系統框才會彈出,使用requestAlwaysAuthorization獲取許可權。

其他變更
  1. 設定UIBarItem.landscapeImagePhone 與UIBarItem.largeContentSizeImage 來適配在豎屏和橫屏下的BarItem圖示和雙擊放大後的圖示,如果使用 PDF 資源圖,系統會自動從PDF資源圖提取相應圖示,就不用設定上述屬性了。
  2. navigationBar.prefersLargeTitles = true 新增大標題屬性,大標題displaymode控制顯示列舉:navigationItem.largeTitleDisplayMode
    列舉屬性:

     - automatic:自動儲存上一次設定的值
     - always:總是顯示大標題
     - never:不顯示複製程式碼
  3. Navigation整合searchBar。navigationItem.searchController 屬性賦值可以整合searchBar在navigationbar下方,navigationItem.hidesSearchBarWhenScrolling屬性控制在滾動時是否自動隱藏。

  4. 確保避免 size 為 0 的自定義view,實現intrinsicContentSize 方法提供預設尺寸。

  5. tableview開啟開啟高度估算(Self-Sizing),設定下面三個屬性,使高度估算失效:

         tableView.estimatedRowHeight = 0 
    
         tableView.estimatedSectionHeaderHeight = 0 
    
         tableView.estimatedSectionFooterHeight = 0複製程式碼
  6. tableview 新增左滑右滑互動。

  7. 廢棄iOS7 以後的layoutMargins,取而代之的是新增的安全區域的概念。
    新增directionalLayoutMargins。對應layoutMargins。
    新增systemMinimumLayoutMargins,當directionalLayout小於systemMinimumLayoutMargins,使用systemMinimumLayoutMargins。
    新增 UIViewController=.viewRespectsSystemMinimumLayoutMargins,預設為FALSE。設為TRUE,可設定任意值。

新仇舊恨

明白了iOS 11大法,再來看看,這大法帶來的各種問題。

NavigationBar問題。

一般使用NavigationBar基本有三種手法。

  1. 純正血統,使用標註控制元件,不在NavigationBar上新增任何的控制元件。
  2. 混血,在NavigationBar上有定製的控制元件。
  3. 毫無血統,完全自定義。隱藏了系統的navigationBar,直接用了View替代。

針對上面三種情況:

  • 使用第一種姿勢的人,很幸運,你不需要進行適配。(估計很少有人不定製)

  • 使用第二種姿勢的人,可能會有返回按鈕、titleView、新增的控制元件position不正確的問題。

  • 使用第三種姿勢的人,很好,在非iPhone X上,也沒什麼大問題。

UIscrollView、UItableView、UICollectionView內容下沉問題。

升級iOS 11後,發現有些使用UItableView佈局的頁面,頂部多出來了20pt或者44pt,也或者64pt。也就是頂部可見Bar的高度的總和。
包括使用MJRefresh引起的問題。也屬於這類。

Xcode9 打出的包(iOS 11 SDK)頁面卡頓問題

升級Xcode9 後,你會發現,基於iOS 11打出來的包,tableView滑動的時候,一卡一頓的。

請求定位框不彈出

在iOS 11有些應用在請求定位時,未能彈出系統請求許可權的對話方塊。

返回按鈕位置偏移問題

iOS 11的的leftBarButtonItem 或者右邊都距離邊距20畫素。

其他問題

  1. 使用YYKit的大圖預覽控制元件YYPhotoGroupView,dismiss的時候,有些頁面會抖一下。

一笑泯恩仇

既然已經知道了iOS 11初出江湖的各種套路和帶來的血雨腥風。那就春風化雨,見招拆招了。

navigationBar 問題。

  • 因為NavigationBar引入了AutoLayout,以往的frame方式可能位置有偏差。以前使用CGRectMakeZero自動撐大已經行不通。那麼使用自動佈局。或者實現下面View的方法,提供預設尺寸。
- (CGSize)intrinsicContentSize {
    return CGSizeMake(100,100);
}複製程式碼
  • 新增到NavigationBarView可能會出各種問題,嘗試新增到contentView上。並設定約束。
  • 返回按鈕問題,請設定好frame在賦值給navigationItem.leftBarButtonItem

UIscrollView、UItableView、UICollectionView內容下沉問題

因為controllerView廢棄了automaticallyAdjustsScrollViewInsets,請使用UIScrollViewContentInsetAdjustmentNever來告訴系統,不要調整。或者設定additionalSafeAreaInsets來增加safeAreaInsets來抵消。

滾動卡頓問題

因為tableView預設開啟Self-Sizing。設定下面三個屬性,使高度估算失效:

    tableView.estimatedRowHeight = 0 
    tableView.estimatedSectionHeaderHeight = 0 
    tableView.estimatedSectionFooterHeight = 0複製程式碼

請求定位框不彈出

在iOSi11,原有的NSLocationAlwaysUsageDeion被降級為NSLocationWhenInUseUsageDeion。因此,在原來專案中使用requestAlwaysAuthorization獲取定位許可權,而未在plist檔案中配置NSLocationAlwaysAndWhenInUseUsageDeion,系統框不會彈出。建議新舊key值都在plist裡配置。

頁面跳動問題

參照內容下沉問題解決。

按鈕偏移問題

iOS 11後,navigationBar新增了contentView來承載開發者新增的barButton。左右兩邊新增了20畫素。現在很幾種解決方案。如果只是想要調節返回按鈕,可以直接使用系統的API:

@property(nullable,nonatomic,strong) UIImage *backIndicatorImage;
@property(nullable,nonatomic,strong) UIImage *backIndicatorTransitionMaskImage;複製程式碼

但是這樣的處理方式,在有多個按鈕的使用情景下就引起問題。20個畫素還是存在的。
還有調整UIButtonimageEdgeInsets的,其實也會在多按鈕的時候出現佈局問題。
比如這篇帖子
也有的在pushpop的時候進行設定的。修改約束會引起有些約束丟失。也有重寫drawRect的

其實不需要這麼麻煩。新建一個類,繼承自UINavigationBar,然後重寫layoutSubviews,如果是ios11,設定contentViewlayoutMargins為需要的值,之前的版本就執行super
核心程式碼如下:

@interface CustomNavigationBar:UINavigationBar
@end


const CGFloat LeftFiexSpace = 0;
const CGFloat RightFiexSpace = 8.0;

@implementation CustomNavigationBar
- (void)layoutSubviews {
    [super layoutSubviews];
    // 修正 ios 11 左右兩邊的邊距
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"11.0")) {
        self.layoutMargins = UIEdgeInsetsZero;
        for (UIView *subview in self.subviews) {
            if ([NSStringFromClass(subview.class) containsString:@"ContentView"]) {
                subview.layoutMargins = UIEdgeInsetsMake(0, LeftFiexSpace, 0, RightFiexSpace);
                [self layoutIfNeeded];
            }
        }
    }
}
@end複製程式碼

在生成NavigationController的地方使用KVC將原來的NavigationBar替換成自己的.

 UINavigationController *nvc = [super initWithRootViewController:rootViewController];    
    CSNavigationBar *naviBar = [[CustomNavigationBar alloc] init];
    [nvc setValue:naviBar forKey:@"navigationBar"];複製程式碼

最後

如果有寫得不對的歡迎指正,有更高好的解決方法,也歡迎交流。

參考文章

蘋果官網適配視訊教程

如何設定返回按鈕

你可能需要為你的APP適配iOS11此篇基本上是官方視訊的文字版

相關文章