題外話
這是一次公司內部技術分享會的內容,內容共分為三個部分:
- 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的同行案列,結合觸手TV
APP實際問題,經過彙總,以下可能是需要適配的點。找到問題的根源,才能幫助我們解決問題。
為什麼會出現上述問題呢?我們看看iOS 11有些什麼新增改動。
這裡只列出部分以及跟今天主題相關的部分。詳情可以看看官網what's new in iOS 11。
UIViewController 與 UIView
主要變化部分:
UIViewController
廢棄LayoutGuide
。iOS7之後,為了輔助Autolayout佈局系統,Apple新增UILayoutSupport
協議。就是被很多人忽略的topLayoutGuide
,bottomLayoutGuide
。
@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
。
簡單理解就是去除系統的各種Bar以及左右UIView
增加safeAreaInsets:UIEdgeInsets
安全區概念。用於替代輔助自動佈局的LayoutGuide
。安全區域定義了佈局View除各種Bar後剩下的可見區域。此屬性是readonly
的,想要改動,需要操作UIViewController
的additionalSafeAreaInsets
屬性。margin(如果有設定過)
後的可用佈局區域。圖中淡藍色區域即為安全區域:
如果在View上的控制元件沒有被遮擋,
注意:如果採用SB、XIB佈局UI的,safeAreaInsets = {(0,0),(0,0)}
分別對應於(top,left,bottom,right)
。
假設子ViewA頂部被navigationBar
遮擋20pt,相應safeAreaInsets = {(20,0),(0,0)}
。safeAreaInsets
最低支援版本為iOS 9.0。考慮到相容性,觸手TV是不能使用安全區域的。UIViewController
廢棄automaticallyAdjustsScrollViewInsets
API。這是個bool值屬性,描述了是否自動適配scrollView的contentInsets。值為true
時,scrollView的內容不會被系統可見的各種bar所遮擋(statusbar
、navigationBar
,tabBar
,toolBar
),一般會引起tableView
,collectionView
,scrollView
的content下移bar的高度值個畫素。如果不需要自動調整,將值設為false
。UIViewController
新增修改關聯View
安全區域的APIadditionalSafeAreaInsets
。可對安全區域的值進行增減,滿足自定義UI的需求。並提供方法viewSafeAreaInsetsDidChange
,用於在安全區域改變後,進行佈局的調整。相應的,
UIScrollView
增加列舉屬性contentInsetAdjustmentBehavior
,描述scrollView
如何調整contentInset(實際是調整adjustedContentInset
屬性),配合安全區域使用。最後UIscrollView
的contentInset值為adjustedContentInset
與safeAreaInsets
之和。有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。NavigationBar
的titleView
支援自動佈局。需要使用者自動撐開新增到上面的View。
定位許可權
在iOS11,原有的NSLocationAlwaysUsageDeion
被降級為NSLocationWhenInUseUsageDeion
。
在.plist
沒檔案中配置NSLocationAlwaysAndWhenInUseUsageDeion
,系統框才會彈出,使用requestAlwaysAuthorization獲取許可權。
其他變更
- 設定UIBarItem.landscapeImagePhone 與UIBarItem.largeContentSizeImage 來適配在豎屏和橫屏下的BarItem圖示和雙擊放大後的圖示,如果使用 PDF 資源圖,系統會自動從PDF資源圖提取相應圖示,就不用設定上述屬性了。
navigationBar.prefersLargeTitles = true 新增大標題屬性,大標題displaymode控制顯示列舉:navigationItem.largeTitleDisplayMode
列舉屬性:- automatic:自動儲存上一次設定的值 - always:總是顯示大標題 - never:不顯示複製程式碼
Navigation整合searchBar。navigationItem.searchController 屬性賦值可以整合searchBar在navigationbar下方,navigationItem.hidesSearchBarWhenScrolling屬性控制在滾動時是否自動隱藏。
確保避免 size 為 0 的自定義view,實現intrinsicContentSize 方法提供預設尺寸。
tableview開啟開啟高度估算(Self-Sizing),設定下面三個屬性,使高度估算失效:
tableView.estimatedRowHeight = 0 tableView.estimatedSectionHeaderHeight = 0 tableView.estimatedSectionFooterHeight = 0複製程式碼
tableview 新增左滑右滑互動。
廢棄iOS7 以後的layoutMargins,取而代之的是新增的安全區域的概念。
新增directionalLayoutMargins。對應layoutMargins。
新增systemMinimumLayoutMargins,當directionalLayout小於systemMinimumLayoutMargins,使用systemMinimumLayoutMargins。
新增 UIViewController=.viewRespectsSystemMinimumLayoutMargins,預設為FALSE。設為TRUE,可設定任意值。
新仇舊恨
明白了iOS 11大法,再來看看,這大法帶來的各種問題。
NavigationBar問題。
一般使用NavigationBar
基本有三種手法。
- 純正血統,使用標註控制元件,不在
NavigationBar
上新增任何的控制元件。 - 混血,在
NavigationBar
上有定製的控制元件。 - 毫無血統,完全自定義。隱藏了系統的
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畫素。
其他問題
- 使用
YYKit
的大圖預覽控制元件YYPhotoGroupView
,dismiss的時候,有些頁面會抖一下。
一笑泯恩仇
既然已經知道了iOS 11初出江湖的各種套路和帶來的血雨腥風。那就春風化雨,見招拆招了。
navigationBar 問題。
- 因為
NavigationBar
引入了AutoLayout
,以往的frame
方式可能位置有偏差。以前使用CGRectMakeZero
自動撐大已經行不通。那麼使用自動佈局。或者實現下面View
的方法,提供預設尺寸。
- (CGSize)intrinsicContentSize {
return CGSizeMake(100,100);
}複製程式碼
- 新增到
NavigationBar
View可能會出各種問題,嘗試新增到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個畫素還是存在的。
還有調整UIButton
的imageEdgeInsets
的,其實也會在多按鈕的時候出現佈局問題。
比如這篇帖子。
也有的在push
和pop
的時候進行設定的。修改約束會引起有些約束丟失。也有重寫drawRect的。
其實不需要這麼麻煩。新建一個類,繼承自UINavigationBar
,然後重寫layoutSubviews
,如果是ios11,設定contentView
的layoutMargins
為需要的值,之前的版本就執行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此篇基本上是官方視訊的文字版