iOS之iOS11、iPhone X、Xcode9 適配指南,ios11iphone
更新iOS11後,發現有些地方需要做適配,整理後按照優先順序分為以下三類:
1.單純升級iOS11後造成的變化;
2.Xcode9 打包後造成的變化;
3.iPhoneX的適配
一、單純升級iOS11後造成的變化
1. 升級後,發現某個擁有tableView的介面錯亂,組間距和contentInset錯亂,因為iOS11中 UIViewController 的 automaticallyAdjustsScrollViewInsets 屬性被廢棄了,因此當tableView超出安全區域時,系統自動會調整SafeAreaInsets值,進而影響adjustedContentInset值
// 有些介面以下使用代理方法來設定,發現並沒有生效- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;// 這樣的原理是因為之前只是實現了高度的代理方法,卻沒有實現View的代理方法,iOS10及以前這麼寫是沒問題的,iOS11開啟了行高估算機制引起的bug,因此有以下幾種解決方案:// 解決方案一:添加實現View的代理方法,只有實現下面兩個方法,方法 (CGFloat)tableView: heightForFooterInSection: 才會生效- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { return nil;}- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { return nil;}// 解決方案二:直接使用tableView屬性進行設定,修複該UI錯亂self.tableView.sectionHeaderHeight = 0;self.tableView.sectionFooterHeight = 5;[_optionTableView setContentInset:UIEdgeInsetsMake(-35, 0, 0, 0)];// 解決方案三:添加以下代碼關閉估算行高self.tableView.estimatedRowHeight = 0;self.tableView.estimatedSectionHeaderHeight = 0;self.tableView.estimatedSectionFooterHeight = 0;
2. 如果使用了Masonry 進行布局,就要適配safeArea
if ([UIDevice currentDevice].systemVersion.floatValue >= 11.0) { make.edges.equalTo(self.view.safeAreaInsets);} else { make.edges.equalTo(self.view);}
3. 對於IM的發送原圖功能,iOS11啟動全新的HEIC 格式的圖片,iPhone7以上裝置+iOS11排出的live照片是".heic"格式圖片,同一張live格式的圖片,iOS10發送就沒問題(轉成了jpg),iOS11就不行
的處理方式是一比一轉化成 jpg 格式
QQ和DingTalk的處理方式是直接壓縮,即使是原圖也壓縮為非原圖
最終採取的是的方案,使用以下代碼轉成jpg格式
// 0.83能保證壓縮前後圖片大小是一致的// 造成不一致的原因是圖片的bitmap一個是8位的,一個是16位的imageData = UIImageJPEGRepresentation([UIImage imageWithData:imageData], 0.83);
二、使用Xcode9 編譯後發現的問題
1. 發現“fastSocket”第三方報錯,具體原因是缺少C99的標頭檔,引入“#include”即可
2. 導覽列的新特性
原生的搜尋欄樣式發生改變
右邊為iOS11樣式,搜尋地區高度變大,字型變大
查看 API 後發現,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 來實現下面的效果,並可以通過 largeTitleTextAttributes 來設定大標題的文本樣式。設定大標題之後,導覽列的高度就會由之前的64pt變成 96pt,如果項目中有直接寫死的高度或者隱藏導覽列之類的操作,就需要適配一下
有個介面使用到了導覽列按鈕相關的frame,也發生了UI錯亂,查看UI層級關係後發現,iOS11以前是直接把按鈕加到了UINavigationBar上面,而iOS11則是先將按鈕加到了_UITAMICAdaptorView,再加到_UIButtonBarStackView、_UINavigationBarContentView,接著才是UINavigationBar。因此如果需要擷取導覽列按鈕 frame 或者 superView,這裡需要專門做下適配
iOS10及以下版本導覽列按鈕層級關係圖
iOS11導覽列按鈕層級關係圖
三、iPhone X的適配
下載完Xcode9之後,第一件事自然是在 iPhone X(模擬器)上過把癮,然後編譯後就發現報錯了
由於iPhone X的狀態列是和其他版本手機差異比較大的,因此api 變化也比較大
先後做了以下適配
適配點一:項目中使用狀態列中表徵圖判斷當前網路的具體狀態
出錯代碼
1 |
列印的 Log 報出以下錯誤: Trapped uncaught exception 'NSUnknownKeyException' , reason: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key foregroundView.' |
iPhone X
其他手機
使用 runtime 列印其所有屬性,發現以下差異
// 測試代碼#import NSMutableString *resultStr = [NSMutableString string];//擷取指定類的Ivar列表及Ivar個數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 = @""key = _showsForeground type = Bkey = _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 = Bkey = _suppressGlow type = Bkey = _translucentBackgroundAlpha type = dkey = _showOnlyCenterItems type = Bkey = _foregroundViewShouldIgnoreStatusBarDataDuringAnimation type = Bkey = _tintColor type = @"UIColor"key = _lastUsedBackgroundColor type = @"UIColor"key = _nextTintTransition type = @"UIStatusBarStyleAnimationParameters"key = _overrideHeight type = @"NSNumber"key = _disableRasterizationReasons type = @"NSMutableSet"key = _timeHidden type = Bkey = _statusBarWindow type = @"UIStatusBarWindow"// iPhone Xkey = _statusBar ; type = @"_UIStatusBar"// 因此可見iPhone X的狀態列是多嵌套了一層,多取一次即可,最終適配代碼為: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]; }
警告以上處理,代碼看起來是不報錯了,然而!!具體看了下代碼發現並不生效!因為從iPhone X取出來之後只有view層級的資訊,因此採用以下方法確定2G/3G/4G,從API上目測是有效
NSArray *typeStrings2G = @[CTRadioAccessTechnologyEdge, CTRadioAccessTechnologyGPRS, CTRadioAccessTechnologyCDMA1x]; NSArray *typeStrings3G = @[CTRadioAccessTechnologyHSDPA, CTRadioAccessTechnologyWCDMA, CTRadioAccessTechnologyHSUPA, CTRadioAccessTechnologyCDMAEVDORev0, CTRadioAccessTechnologyCDMAEVDORevA, CTRadioAccessTechnologyCDMAEVDORevB, CTRadioAccessTechnologyeHRPD]; NSArray *typeStrings4G = @[CTRadioAccessTechnologyLTE]; // 該 API 在 iOS7 以上系統才有效 if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) { CTTelephonyNetworkInfo *teleInfo= [[CTTelephonyNetworkInfo alloc] init]; NSString *accessString = teleInfo.currentRadioAccessTechnology; if ([typeStrings4G containsObject:accessString]) { NSLog(@"4G網路"); } else if ([typeStrings3G containsObject:accessString]) { NSLog(@"3G網路"); } else if ([typeStrings2G containsObject:accessString]) { NSLog(@"2G網路"); } else { NSLog(@"未知網路"); } } else { NSLog(@"未知網路"); }
適配點二:解決這個問題後項目跑起來發現,整個app介面上下各空出大概40pt的高度
經常從 Github 上下載項目把玩的老司機們都知道,有些老項目在模擬器上跑起來之後也會只有 iPhone 4(320480)的布局空間,造成這個的原因是啟動圖使用 Launch Images Source 設定的時候沒有勾選並設定對應的圖片(11252436),解決方案如下
但是即使按照上面的操作進行之後,會發現底部 UITabBar 依舊是高出一些高度,查看層級關係後發現,同樣是由於安全區的原因,UITabBar 高度由49pt變成了83pt,因此這裡也要對iPhone X 及其模擬器進行適配
適配點三:iPhone X 只有 faceID,沒有touchID,如果in的應用有使用到 touchID 解鎖的地方,這裡要根據機型進行相應的適配
適配點四:之前有偷懶的直接使用20替代狀態列高度,這些坑都要通過重新擷取狀態列高度
CGRectGetHeight([UIApplication sharedApplication].statusBarFrame)
適配點五:然而iPhone X更大的坑是螢幕的適配
首先看下螢幕尺寸
這張圖反映出不少資訊:
iPhone X的寬度雖然和7是一樣的,但是高度多出145pt
使用三倍圖是重點,而且一般認為肉眼所能所能識別的最高的螢幕密度是300ppi,iPhone X已達到458ppi(查證發現三星galaxy系列的螢幕密度是522ppi)
在設計方面,蘋果官方文檔human-interface-guidelines有明確要求,下面結合圖例進行說明:
展示出來的設計布局要求填滿整個螢幕
填滿的同時要注意控制項不要被大圓角和感應器部分所遮擋
安全區域以外的部分不允許有任何與使用者互動的控制項
上面這張圖內含資訊略多
頭部導覽列不予許進行使用者互動的,意味著下面這兩種情況 Apple 官方是不允許的
底部虛擬區是替代了傳統home鍵,高度為34pt,通過上滑可呼起多任務管理,考慮到手勢衝突,這部分也是不允許有任何可互動的控制項,但是設計的背景圖要覆蓋到非安全區域
狀態列在非安全區域,文檔中也提到,除非可以通過隱藏狀態列給使用者帶來額外的價值,否則最好把狀態列還給使用者
不要讓 介面中的元素 幹擾底部的主畫面指標
重複使用現有圖片時,注意長寬比差異。iPhone X 與常規 iPhone 的螢幕長寬比不同,因此,全屏的 4.7 寸屏映像在 iPhone X 上會出現裁切或適配寬度顯示。所以,這部分的視圖需要根據裝置做出適配。
注意類似佔位圖的適配
適配點六:橫屏適配
關於 safe area,使用 safeAreaLayoutGuide 和 safeAreaInset就能解決大部分問題,但是橫屏下還可能會產生一些問題,需要額外適配
產生這個原因代碼是:[headerView.contentView setBackgroundColor:[UIColor headerFooterColor]],這個寫法看起來沒錯,但是只有在 iPhone X上有問題
通過設定該屬性,內容視圖嵌入到了safe area中,但是contentView沒有
解決方案:設定backgroundView顏色 [headerView.backgroundView setBackgroundColor:[UIColor headerFooterColor]]
適配點七:裝置資訊
if ([deviceString isEqualToString:@"iPhone10,1"]) return @"國行(A1863)、日行(A1906)iPhone 8";if ([deviceString isEqualToString:@"iPhone10,4"]) return @"美版(Global/A1905)iPhone 8";if ([deviceString isEqualToString:@"iPhone10,2"]) return @"國行(A1864)、日行(A1898)iPhone 8 Plus";if ([deviceString isEqualToString:@"iPhone10,5"]) return @"美版(Global/A1897)iPhone 8 Plus";if ([deviceString isEqualToString:@"iPhone10,3"]) return @"國行(A1865)、日行(A1902)iPhone X";if ([deviceString isEqualToString:@"iPhone10,6"]) return @"美版(Global/A1901)iPhone X";