2017.09.23ios
不斷完善中。。。git
2017.10.02 新增 iPhone X 適配官方中文文檔github
更新iOS11後,發現有些地方須要作適配,整理後按照優先級分爲如下三類:api
######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;
複製代碼
if ([UIDevice currentDevice].systemVersion.floatValue >= 11.0) {
make.edges.equalTo(self.view.safeAreaInsets);
} else {
make.edges.equalTo(self.view);
}
複製代碼
.heic
格式圖片,同一張live格式的圖片,iOS10發送就沒問題(轉成了jpg),iOS11就不行// 0.83能保證壓縮先後圖片大小是一致的
// 形成不一致的緣由是圖片的bitmap一個是8位的,一個是16位的
imageData = UIImageJPEGRepresentation([UIImage imageWithData:imageData], 0.83);
複製代碼
// 這是由於 UIScrollView 的 contentInsetAdjustmentBehavior 屬性默認爲 automatic,經過如下代碼能夠修復
if (@available(iOS 11.0, *)) {
self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
// 固然,若是是使用 Storyboard,能夠依次 Size Inspector -> Content Insets -> Set 'Never' 搞定
複製代碼
進行修改以後,沒有 SearchViewController 的頁面是沒有問題的,可是擁有searchViewController 的頁面,進行搜索文本的輸入會形成UI錯亂,所以使用如下解決方法安全
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (@available(iOS 11.0, *)) {
self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (@available(iOS 11.0, *)) {
self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic;
}
}
複製代碼
前往文件 "UITableView+FDTemplateLayoutCell.h" 70行
if (isSystemVersionEqualOrGreaterThen10_2) {
// 將這裏的 UILayoutPriorityRequired 更改成 UILayoutPriorityDefaultHigh 便可解決問題
widthFenceConstraint.priority = UILayoutPriorityDefaultHigh - 1;
}
複製代碼
UITableViewRowActionStyleNormal
,改成UITableViewRowActionStyleDestructive
便可deleteRowsAtIndexPaths
方法保持UI上的同步UITableViewRowAction *deleteRowAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@"刪除" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
[self.dataSource removeObjectAtIndex:indexPath.row];
// 刷新tableview
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
}
複製代碼
fastSocket
第三方報錯,具體緣由是缺乏C99的頭文件,引入#include <sys/time.h>
便可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
,這裏須要專門作下適配網絡
下載完Xcode9以後,第一件事天然是在 iPhone X(模擬器)上過把癮,而後編譯後就發現報錯了 因爲iPhone X的狀態欄是和其餘版本手機差別比較大的,所以api 變化也比較大 前後作了如下適配app
打印的 Log 報出如下錯誤: Trapped uncaught exception 'NSUnknownKeyException', reason: '[<UIStatusBar_Modern 0x7fcdb0805770> valueForUndefinedKey:]: this class is not key value coding-compliant for the key foregroundView.'iphone
使用 runtime 打印其全部屬性,發現如下差別
// 測試代碼
#import <objc/runtime.h>
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 = @"<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的狀態欄是多嵌套了一層,多取一次便可,最終適配代碼爲:
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(@"未知網絡");
}
複製代碼
常常從 Github 上下載項目把玩的老司機們都知道,有些老項目在模擬器上跑起來以後也會只有 iPhone 4(320480)的佈局空間,形成這個的緣由是啓動圖使用 Launch Images Source 設置的時候沒有勾選並設置對應的圖片(11252436),解決方法以下
可是即便按照上面的操做進行以後,會發現底部 UITabBar 依舊是高出一些高度,查看層級關係後發現,一樣是因爲安全區的緣由,UITabBar 高度由49pt變成了83pt,所以這裏也要對iPhone X 及其模擬器進行適配
CGRectGetHeight([UIApplication sharedApplication].statusBarFrame)
複製代碼
首先看下屏幕尺寸
這張圖反映出很多信息:在設計方面,蘋果官方文檔human-interface-guidelines有明確要求,下面結合圖例進行說明:
上面這張圖內含信息略多關於 safe area,使用 safeAreaLayoutGuide 和 safeAreaInset就能解決大部分問題,可是橫屏下還可能會產生一些問題,須要額外適配
[headerView.contentView setBackgroundColor:[UIColor headerFooterColor]]
,這個寫法看起來沒錯,可是隻有在 iPhone X上有問題[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";
複製代碼
更多新設備信息詳見**Github-iOS-getClientInfo**
// 在VC裏面重寫下面這個方法便可
- (BOOL)prefersHomeIndicatorAutoHidden{
return YES;
}
複製代碼