iOS11、iPhone X、Xcode9 適配指南

更新iOS11后,發(fā)現(xiàn)有些地方需要做適配,整理后按照優(yōu)先級分為以下三類:

  1. 單純升級iOS11后造成的變化;
  2. Xcode9 打包后造成的變化;
  3. iPhoneX的適配

一、單純升級iOS11后造成的變化

  1. 升級后,發(fā)現(xiàn)某個擁有tableView的界面錯亂,組間距和contentInset錯亂,因為iOS11中 UIViewControllerautomaticallyAdjustsScrollViewInsets 屬性被廢棄了,因此當(dāng)tableView超出安全區(qū)域時,系統(tǒng)自動會調(diào)整SafeAreaInsets值,進而影響adjustedContentInset
// 有些界面以下使用代理方法來設(shè)置,發(fā)現(xiàn)并沒有生效
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
// 這樣的原理是因為之前只是實現(xiàn)了高度的代理方法,卻沒有實現(xiàn)View的代理方法,iOS10及以前這么寫是沒問題的,iOS11開啟了行高估算機制引起的bug,因此有以下幾種解決方法:

// 解決方法一:添加實現(xiàn)View的代理方法,只有實現(xiàn)下面兩個方法,方法 (CGFloat)tableView: heightForFooterInSection: 才會生效
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { return nil;}- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
 return nil;
}

// 解決方法二:直接使用tableView屬性進行設(shè)置,修復(fù)該UI錯亂
self.tableView.sectionHeaderHeight = 0;
self.tableView.sectionFooterHeight = 5;
[_optionTableView setContentInset:UIEdgeInsetsMake(-35, 0, 0, 0)];

// 解決方法三:添加以下代碼關(guān)閉估算行高
self.tableView.estimatedRowHeight = 0;
self.tableView.estimatedSectionHeaderHeight = 0;
self.tableView.estimatedSectionFooterHeight = 0;
  1. 如果使用了Masonry 進行布局,就要適配safeArea
if ([UIDevice currentDevice].systemVersion.floatValue >= 11.0) { 
    make.edges.equalTo(self.view.safeAreaInsets);
} else { 
    make.edges.equalTo(self.view);
}

四、使用Xcode9 編譯后發(fā)現(xiàn)的問題
1. 發(fā)現(xiàn)“fastSocket”第三方報錯,具體原因是缺少C99的頭文件,引入“#include <sys/time.h>”即可

2. 導(dǎo)航欄的新特性

  • 原生的搜索欄樣式發(fā)生改變
右邊為iOS11樣式,搜索區(qū)域高度變大,字體變大

查看 API 后發(fā)現(xiàn),iOS11后將 searchController 賦值給了 NavigationItem,通過屬性 hidesSearchBarWhenScrolling 可以控制搜索欄是否在滑動的時候進行隱藏和顯示

// A view controller that will be shown inside of a navigation controller can assign a UISearchController to this property to display the search controller’s search bar in its containing navigation controller’s navigation bar.
@property (nonatomic, retain, nullable) UISearchController *searchController API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);

// If this property is true (the default), the searchController’s search bar will hide as the user scrolls in the top view controller’s scroll view. If false, the search bar will remain visible and pinned underneath the navigation bar.
@property (nonatomic) BOOL hidesSearchBarWhenScrolling API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);
  • 另外,UINavigationBar 新增屬性 BOOL值 prefersLargeTitles 來實現(xiàn)下面的效果,并可以通過 largeTitleTextAttributes 來設(shè)置大標(biāo)題的文本樣式。設(shè)置大標(biāo)題之后,導(dǎo)航欄的高度就會由之前的64pt變成 96pt,如果項目中有直接寫死的高度或者隱藏導(dǎo)航欄之類的操作,就需要適配一下

有個界面使用到了導(dǎo)航欄按鈕相關(guān)的frame,也發(fā)生了UI錯亂,查看UI層級關(guān)系后發(fā)現(xiàn),iOS11以前是直接把按鈕加到了UINavigationBar上面,而iOS11則是先將按鈕加到了_UITAMICAdaptorView,再加到_UIButtonBarStackView_UINavigationBarContentView,接著才是UINavigationBar。因此如果需要獲取導(dǎo)航欄按鈕 frame 或者 superView,這里需要專門做下適配

iOS10及以下版本導(dǎo)航欄按鈕層級關(guān)系圖
iOS11導(dǎo)航欄按鈕層級關(guān)系圖

三、iPhone X的適配

下載完Xcode9之后,第一件事自然是在 iPhone X(模擬器)上過把癮,然后編譯后就發(fā)現(xiàn)報錯了由于iPhone X的狀態(tài)欄是和其他版本手機差異比較大的,因此api 變化也比較大先后做了以下適配
適配點一:項目中使用狀態(tài)欄中圖標(biāo)判斷當(dāng)前網(wǎng)絡(luò)的具體狀態(tài)

出錯代碼

出錯代碼

打印的 Log 報出以下錯誤: Trapped uncaught exception 'NSUnknownKeyException', reason: '[<UIStatusBar_Modern 0x7fcdb0805770> valueForUndefinedKey:]: this class is not key value coding-compliant for the key foregroundView.'

iPhone X

iPhone X

其他手機

其他手機

使用 runtime 打印其所有屬性,發(fā)現(xiàn)以下差異

// 測試代碼
#import <objc/runtime.h>
NSMutableString *resultStr = [NSMutableString string];

//獲取指定類的Ivar列表及Ivar個數(shù)
unsigned int count = 0;
Ivar *member = class_copyIvarList([[application valueForKeyPath:@"_statusBar"] class], &count); 
for(int i = 0; i < count; i++){
     Ivar var = member[i]; 
    //獲取Ivar的名稱 
    const char *memberAddress = ivar_getName(var); 
    //獲取Ivar的類型 const char *memberType = ivar_getTypeEncoding(var); 
    NSString *str = [NSString stringWithFormat:@"key = %s type = %s \n",memberAddress,memberType]; 
    [resultStr appendString:str];
 }
NSLog(@"%@", resultStr);
// 其他版本的手機
key = _inProcessProvider type = @"<UIStatusBarStateProvider>"
key = _showsForeground type = B
key = _backgroundView type = @"UIStatusBarBackgroundView"
key = _doubleHeightLabel type = @"UILabel"
key = _doubleHeightLabelContainer type = @"UIView"
key = _currentDoubleHeightText type = @"NSString"
key = _currentRawData type = {超長。。}
key = _interruptedAnimationCompositeViews type = @"NSMutableArray"
key = _newStyleBackgroundView type = @"UIStatusBarBackgroundView"
key = _newStyleForegroundView type = @"UIStatusBarForegroundView"
key = _slidingStatusBar type = @"UIStatusBar"
key = _styleAttributes type = @"UIStatusBarStyleAttributes"
key = _waitingOnCallbackAfterChangingStyleOverridesLocally type = B
key = _suppressGlow type = B
key = _translucentBackgroundAlpha type = d
key = _showOnlyCenterItems type = B
key = _foregroundViewShouldIgnoreStatusBarDataDuringAnimation type = B
key = _tintColor type = @"UIColor"
key = _lastUsedBackgroundColor type = @"UIColor"
key = _nextTintTransition type = @"UIStatusBarStyleAnimationParameters"
key = _overrideHeight type = @"NSNumber"key = _disableRasterizationReasons type = @"NSMutableSet"key = _timeHidden type = B
key = _statusBarWindow type = @"UIStatusBarWindow"
// iPhone X
key = _statusBar ; type = @"_UIStatusBar"
// 因此可見iPhone X的狀態(tài)欄是多嵌套了一層,多取一次即可,最終適配代碼為:
NSArray *children;
// 不能用 [[self deviceVersion] isEqualToString:@"iPhone X"] 來判斷,因為iPhone X 的模擬器不會返回 iPhone X 
if ([[application valueForKeyPath:@"_statusBar"] isKindOfClass:NSClassFromString(@"UIStatusBar_Modern")]) { 
    children = [[[[application valueForKeyPath:@"_statusBar"] valueForKeyPath:@"_statusBar"] valueForKeyPath:@"foregroundView"] subviews]; 
} else {
     children = [[[application valueForKeyPath:@"_statusBar"] valueForKeyPath:@"foregroundView"] subviews]; 
}

適配點二:解決這個問題后項目跑起來發(fā)現(xiàn),整個app界面上下各空出大概40pt的高度

經(jīng)常從 Github 上下載項目把玩的老司機們都知道,有些老項目在模擬器上跑起來之后也會只有 iPhone 4(320480)的布局空間,造成這個的原因是啟動圖使用 Launch Images Source 設(shè)置的時候沒有勾選并設(shè)置對應(yīng)的圖片(11252436),解決方法如下

但是即使按照上面的操作進行之后,會發(fā)現(xiàn)底部 UITabBar 依舊是高出一些高度,查看層級關(guān)系后發(fā)現(xiàn),同樣是由于安全區(qū)的原因,UITabBar 高度由49pt變成了83pt,因此這里也要對iPhone X 及其模擬器進行適配

適配點三:iPhone X 只有 faceID,沒有touchID,如果in的應(yīng)用有使用到 touchID 解鎖的地方,這里要根據(jù)機型進行相應(yīng)的適配

適配點四:之前有偷懶的直接使用20替代狀態(tài)欄高度,這些坑都要通過重新獲取狀態(tài)欄高度

CGRectGetHeight([UIApplication sharedApplication].statusBarFrame)

適配點五:然而iPhone X更大的坑是屏幕的適配
首先看下屏幕尺寸

這張圖反映出不少信息:

  • iPhone X的寬度雖然和7是一樣的,但是高度多出145pt
  • 使用三倍圖是重點,而且一般認為肉眼所能所能識別的最高的屏幕密度是300ppi,iPhone X已達到458ppi(查證發(fā)現(xiàn)三星galaxy系列的屏幕密度是522ppi)

在設(shè)計方面,蘋果官方文檔human-interface-guidelines有明確要求,下面結(jié)合圖例進行說明:

展示出來的設(shè)計布局要求填滿整個屏幕

填滿的同時要注意控件不要被大圓角和傳感器部分所遮擋
安全區(qū)域以外的部分不允許有任何與用戶交互的控件

上面這張圖內(nèi)含信息略多

  • 頭部導(dǎo)航欄不予許進行用戶交互的,意味著下面這兩種情況 Apple 官方是不允許的


  • 底部虛擬區(qū)是替代了傳統(tǒng)home鍵,高度為34pt,通過上滑可呼起多任務(wù)管理,考慮到手勢沖突,這部分也是不允許有任何可交互的控件,但是設(shè)計的背景圖要覆蓋到非安全區(qū)域
  • 狀態(tài)欄在非安全區(qū)域,文檔中也提到,除非可以通過隱藏狀態(tài)欄給用戶帶來額外的價值,否則最好把狀態(tài)欄還給用戶

原文鏈接:http://www.itdecent.cn/p/f5ee206c7df0明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容