iOS開發--iOS11&&iPhoneX適配

一、前言

iOS11發布也有一段時間了,每次版本升級,相關的適配工作當然是下個版本的核心工作之一。而且這次iOS11的更新,相對于iOS10的更新來說,改動點還是比較多的。除了iOS11系統的更新之外,iPhoneX劉海的打理工作也是必不可少。以前我們總是慶幸作為iOS開發者,不必像Android開發者需要考慮各種不同機型的適配問題。但是現在,隨著iPhone各種歷史版本的存在,各種花式的新版本產生,不同版本之間的適配問題,也是未來我們作為iOS開發者必然要考慮的重要問題之一。

這次我主要負責我們這邊兩款App(滴滴代駕司機端+駕管App)iOS11&iPhoneX適配工作。中間也躺過很多坑,一一記錄了下來寫成這篇文章,既是對自己工作的一次總結,也可以分享給其他iOS開發者,能夠讓大家少趟一些坑。

本文將分為三個部分,分別從三方庫適配、UI適配、權限適配、補充知識等方面分別進行展開。

二、三方庫適配問題。

2.1 CocoaLumberjack 編譯出錯

1.png

CocoaLumberjack編譯報錯

問題原因:

從錯誤提示可以看出在Xcode9中os_log_error的第二個參數format必須要為不可變的string類型,而不是char*。

解決方案:

我們只要改成如下形式就可以了
os_log_error(OS_LOG_DEFAULT, "%s", msg);

如果你的工程是pod依賴的話,將pod版本升級到3.3.0版本即可。

2.2 WebViewJavascriptBridge崩潰處理

我們代駕司機端web容器使用的是WKWebView,jsBridge使用的是WebViewJavascriptBridge這個三方庫,更新到Xcode9之后,只要進入WKWebView容器,就會產生如下crash:

1.png

WebViewJavascriptBridge crash

問題原因:

當你使用WKWebView作為你的H5容器的時候,WKNavigationDelegate有個回調就是

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void(^)(WKNavigationActionPolicy))decisionHandler;

這個回調主要負責根據webView、navigationAction相關信息決定這次跳轉是否可以繼續進行。調用decisionHandler(WKNavigationActionPolicyAllow);響應這次跳轉請求。調用decisionHandler(WKNavigationActionPolicyCancel);就是不響應這次跳轉請求。

查看WebViewJavascriptBridge源碼可以看出,在WKWebViewJavascriptBridge.m文件中

WKVebViewCrash.png

在Xcode9中,如果連續看了兩次調用decisionHandler方法就會crash。這個問題在之前版本的Xcode均是沒有問題的。

解決方案:

  • 方案一:修改源碼

    在上面代碼的149行和150行之間添加return;

  • 方案二:pod依賴,原作者沒有修改此問題,無法修改源碼,也可以在業務代碼中進行規避。

    在你自己業務代碼的對應對調中添加排除代碼,如下:

WKWebViewCrash2.png

2.3 LumberjackConsole UI適配

1.png

問題原因:

iOS7之后,如何你設置self.edgesForExtendedLayout = UIRectEdgeNone的話,系統通過設置UIViewController的automaticallyAdjustsScrollViewInsets屬性來自動調整UIScrollView的contentInset,使UIscrollView能夠呈現在我們的可是范圍之內,而不會被navBar擋住。這個屬性在iOS11中被廢棄掉了,在iOS11中代替該屬性功能的則是UIScrollView類中的contentInsetAdjustmentBehavior和adjustedContentInset屬性.在iOS11中用來決定scrollView超出安全區域與邊緣距離的屬性是adjustedContentInset而不是contentInset。當scrollView超出安全區域時系統會自動調整SafeAreaInsets值,進而影

響 adjustedContentInset,所以導致scrollView下移20pt或者64pt。當使用自定義的 navigationbar,并且scrollView的frame超出安全區域,SafeAreaInsets為(20,0,0,0);當使用系統的navigationbar,SafeAreaInsets為(64,0,0,0)。

解決方案:

在UIScrollView或者UITableView初始化的地方,加入如下代碼即可。

針對LumberjackConsole這個開源庫,我們可以在PTEConsoleTableView.m文件中的commonInit最后加入如下代碼即可。

if([self respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]){
[self setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
}

三、UI適配

3.1 NavBar中右上角的customView產生偏移

1.png

問題原因:

在iOS11中,新的導航視圖,使用了AutoLayout布局。而我們這邊右上角的兩個按鈕組合成一個customView,然后把這個customView設置給setRightBarButtonItems而來。customView內部都是frame布局,所以在自動布局下面出錯。

解決方案:

NavBar中的customView里面針對iOS11,均要采用自動布局。

1.png

3.2 NavBar中自定義TitleView產生偏移

1.png

問題原因:

同上

解決方案:

同上

這里需要注意一點,自動布局的UI是延遲設置frame的。如果aView采用自動布局。然后你馬上調用它的aView.bounds是不正確的。

3.3 NavBar中按鈕的響應區域都變小了。

1.png

問題原因:

iOS11之前,雖然我們設置了NavBar上每一個[btn sizeToFit]。蘋果依然會幫我們把每一個按鈕的點擊區域擴大,可以點擊區域如上圖綠色區域所示。但是在iOS11中,你的按鈕的bounds為多大,那你的點擊區域就只有多大。估計這個改動也與這次NavBar的大概有關系。

解決方案:

擴大每一個btn的bounds,而不要使用sizeToFit方法。

3.4 NavBar的BarButtonItem無法貼邊。有(非plus手機16pt,plus手機20pt)的區域浪費。造成UI偏移,并且最左側和最右側區域無法點擊。

1.png

問題原因:

這個UINavigationBarContentView平鋪在導航欄中作為iOS11的各個按鈕的父視圖,該視圖的所有的子視圖都會有一個layoutMargins被占用,也就是系統調整的占位。

解決方案:

去掉系統默認占位。

系統并沒有提供我們直接去掉系統默認占位的方法,那怎么做呢?

我們新建一個UINavigationBar的分類,hook住UINavigationBar的layoutSubviews方法。然后遍歷View,重新設置layoutMargin約束。

1.png

重新設置layoutMargin約束

3.5 UITableView 默認開啟Self-Sizing,導致UI顯示有問題。

1.png

問題原因:

在iOS11中UITableView會默認使 Self-Sizing,這會導致tableView

的 estimatedRowHeight 、 estimatedSectionHeaderHeight 、 estimatedSectionFooterHeight 的高度估算屬性由默認的0變成 UITableViewAutomaticDimension ,reloadData時可能會導致最后顯示的contentSize與預想的不一致;

同時在iOS11中如果不實現 -tableView: viewForHeaderInSection: 和 tableView: viewForFooterInSection: 方法,則 -tableView: heightForHeaderInSection: 和 - tableView: heightForFooterInSection: 不會被調用,而iOS11之前則沒問題。上述都可能會導致界面出現錯亂。

解決方案:

單獨關閉摸一個UITableView的Self-Sizing。

_tableView.estimatedRowHeight = 0;

_tableView.estimatedSectionFooterHeight = 0;

_tableView.estimatedSectionHeaderHeight = 0;

關閉所有的UIScroolView、UITableView和UICollectionView的Self-Sizing:

UIScrollView.appearance.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;

UITableView.appearance.estimatedRowHeight = 0;

UITableView.appearance.estimatedSectionFooterHeight = 0;

UITableView.appearance.estimatedSectionHeaderHeight = 0;

3.6 keyWindow獲取錯誤, 導致UI問題。

1.png

問題原因:

機器貓圖標是一個UIWindow,windowLevel級別比UIWindowLevelStatusBar還高,所以可以常駐UI最上方。第一次進入該頁面,點擊“更多”,彈出popView,點擊收藏,彈出系統UIAlertView,此時UIAlertView變成了keyWindow。當UIAlertView消失的時候,keyWindow會被誰接管呢?

iOS11之前,彈出UIAlertView之前的keyWindow是[[UIApplication sharedApplication].delegate window],那么消失的時候,keyWindow還是[[UIApplication sharedApplication].delegate window]。

iOS11, 彈出UIAlertView之前的keyWindow是[[UIApplication sharedApplication].delegate window],那么消失的時候,keyWindow變成z軸最高的UIWindow,即變成了機器貓那個window。所以導致popView被添加到機器貓window中,造成UI樣式問題。

解決方案:

重寫自定義UIWindow的becomeKeyWindow的方法,每次自定義window將會變為keyWindow的時候,把keyWindow改成[[UIApplication sharedApplication].delegate window]。

- (void)becomeKeyWindow{

UIWindow *appWindow = [[UIApplication sharedApplication].delegate window];

[appWindow makeKeyWindow];

}

3.7 狀態欄高度寫死為20pt,導致在iPhoneX上面遮擋住statusBar。

1.png

問題原因:

iPhoneX上的statusBar的高度為44pt,跟其他iPhone型號的20pt不一樣。所以以后我們在以statusBar為定位點的時候,不能寫死20pt。而要使用[UIApplication sharedApplication].statusBarFrame.size.height來獲取,為了方便,可以定義為宏,放到pch文件中,如下:

#define kApplicationStatusBarHeight [UIApplication sharedApplication].statusBarFrame.size.height//狀態欄的高度`

解決方案:

狀態欄高度定位的時候不要寫死20,要使用[UIApplication sharedApplication].statusBarFrame.size.height來獲取。

補充

iOS11之前導航欄默認高度為64pt(這里高度指statusBar + NavigationBar),iOS11之后如果設置 prefersLargeTitles = YES則為96pt,默認情況下還是64pt,但在iPhoneX上由于劉海的出現 statusBar由以前的20pt變成 44pt,所以iPhoneX上高度變為88pt,如果項目里隱藏了導航欄加了自定義按鈕之類的,這里需要注意適配一下。

3.8 通過遍歷statusBar的subviews中的UIStatusBarDataNetworkItemView獲取網絡狀態在iPhoneX上會crash。

問題原因:

之前我們采用遍歷statusBar,獲取UIStatusBarDataNetworkItemView實例,再獲取網絡狀態的。代碼如下:
+ (NSNumber *) dataNetworkTypeFromStatusBar {

UIApplication *app = [UIApplication sharedApplication];

NSArray *subviews = [[[app valueForKey:@"statusBar"] valueForKey:@"foregroundView"] subviews];

NSNumber *dataNetworkItemView = nil;

@try{

if ([subviews count] > 0) {

for (id subview in subviews) {

if([subview isKindOfClass:[NSClassFromString(@"UIStatusBarDataNetworkItemView") class]]) {

dataNetworkItemView = subview;

break;

}

}

}

}

@catch(NSException *exception) {

}

@finally {

}

return [dataNetworkItemView valueForKey:@"dataNetworkType"];

}

|

但是在iphoneX的statusBar的內部結構已經改變,不能根據遍歷獲取UIStatusBarDataNetworkItemView的狀態獲取網絡狀態狀態。

可以通過po [statusBar recursiveDescription]打印出來iphoneX內部結構了,可以看出變化非常的大。

解決方案:

使用AFNetworking中的AFNetworkReachabilityManager類,或者使用Reachability庫獲取網絡連接狀態。

四、權限適配

4.1 無法獲取定位信息,第一次打開app也無法彈出定位權限提示框

問題原因:

iOS11 定位相關的權限做了更改,在iOS11上使用了新的定位權限key。

解決方案:

如果原來申請的權限是始終允許NSLocationAlwaysUsageDescription,那么需要在保留原來的key的基礎上增加NSLocationWhenInUseUsageDescription和NSLocationAlwaysAndWhenInUsageDescription。

五、其他一些補充

5.1 如何判斷該設備是不是iPhoneX

1.png

5.2 一些常用的宏定義

#define IS_IPHONE_X [KDDeviceHelper is_iPhone_X]

#define IPHONE_NAVIGATIONBAR_HEIGHT (IS_IPHONE_X ? 88: 64)

#define IPHONE_STATUSBAR_HEIGHT (IS_IPHONE_X ? 44: 20)

#define IPHONE_SAFEBOTTOMAREA_HEIGHT (IS_IPHONE_X ? 34: 0)

#define IPHONE_TOPSENSOR_HEIGHT (IS_IPHONE_X ? 32: 0)

轉發自此文特此聲明,如有問題,請聯系我!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容