iOS 11適配源碼 Demo地址
安全區(qū)域的適配
用Xcode 9 創(chuàng)建storyboard或者xib時(shí),最低版本支持iOS 8時(shí)會(huì)報(bào): Safe Area Layout Guide before iOS 9.0 如圖:

原因:在iOS7中引入的Top Layout Guide和Bottom Layout Guide,這些布局指南在iOS 11中被棄用,取而代之的是Safe Area Layout Guide.
當(dāng)一個(gè)Viewcontroller 被嵌入到UINavigationcontroller 、Tab bar 或者ToolBar 中時(shí), 我們可以使用 Top Layout Guide 和 Bottom Layout Guide 讓 view根 據(jù)上下錨點(diǎn)自適應(yīng)內(nèi)容。如圖:

在iOS 11中取而代之的是Safe Area Layout Guide,在iOS11中蘋果用單獨(dú)的Safe Area屬性代替了上面的屬性.安全區(qū)域限制于頂部和底部的錨點(diǎn)。如圖:

解決辦法:適配最低支持版本iOS 8,將圖中的 Use Safe Area Layout Guide 取消即可
可以通過一個(gè)新的屬性:addtionalSafeAreaInsets來改變safeAreaInsets的值,當(dāng)你的viewController改變了它的safeAreaInsets值時(shí),有兩種方式獲取到回調(diào):
UIView.safeAreaInsetsDidChange()
UIViewController.viewSafeAreaInsetsDidChange()
如果你的APP中是自定義的Navigationbar,隱藏掉系統(tǒng)的Navigationbar,并且tableView的frame為(0, 0, kSCREEN_WIDTH, kSCREEN_HEIGHT)開始,那么系統(tǒng)會(huì)自動(dòng)調(diào)整SafeAreaInsets值為(20,0,0,0),如果使用了系統(tǒng)的navigationbar,那么SafeAreaInsets值為(64,0,0,0),如果也使用了系統(tǒng)的tabbar,那么SafeAreaInsets值為(64,0,49,0)
UIScrollView、UITableView、UICollectionView適配
UITableView
Tableview莫名奇妙的偏移20pt或者64pt, 原因是iOS11棄用了automaticallyAdjustsScrollViewInsets屬性,取而代之的是UIScrollView新增了contentInsetAdjustmentBehavior屬性,這一切的罪魁禍?zhǔn)锥际切乱氲膕afeArea
適配:
// 定義宏
#define adjustsScrollViewInsets(scrollView)\
do {\
_Pragma("clang diagnostic push")\
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")\
if ([scrollView respondsToSelector:NSSelectorFromString(@"setContentInsetAdjustmentBehavior:")]) {\
NSMethodSignature *signature = [UIScrollView instanceMethodSignatureForSelector:@selector(setContentInsetAdjustmentBehavior:)];\
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];\
NSInteger argument = 2;\
invocation.target = scrollView;\
invocation.selector = @selector(setContentInsetAdjustmentBehavior:);\
[invocation setArgument:&argument atIndex:2];\
[invocation retainArguments];\
[invocation invoke];\
}\
_Pragma("clang diagnostic pop")\
} while (0)
如果你使用了Masonry,那么你需要適配safeArea
if (@available(iOS 11.0, *)) {
make.edges.equalTo()(self.view.safeAreaInsets)
} else {
make.edges.equalTo()(self.view)
}
- 首先結(jié)構(gòu)發(fā)生了變化:對(duì)比


適配:設(shè)置TableView的高度為全屏,會(huì)自動(dòng)適配。
- 有點(diǎn)Eclipse的味道了,遵守代理后,點(diǎn)擊fix會(huì)自動(dòng)填充完所需方法

自動(dòng)實(shí)現(xiàn)所需方法效果(貌似實(shí)現(xiàn)了很多不常用的):
- 代理方法的優(yōu)化:
iOS 11之前不設(shè)置sectionHeaderView或者sectionFooterView會(huì)走設(shè)置高度的方法。
iOS 11 如果不實(shí)現(xiàn)下面這兩個(gè)方法,不會(huì)走設(shè)置高度的方法,即:設(shè)置高度失效。
當(dāng)TableView時(shí)分組樣式Grouped,設(shè)置高度為不等0的很小的數(shù)也會(huì)失效。所以必須實(shí)現(xiàn)下面兩個(gè)方法和并設(shè)置高度。
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
- iOS 11默認(rèn)開啟了Self-Sizing(在WWDC 2017 session204 Updating Your App for iOS 11 中有介紹),也是造成上面代理方法優(yōu)化的問題的根本原因,同時(shí)在獲取TableView的ContentSize也不再準(zhǔn)確的大小。均是UITableViewAutomaticDimension 預(yù)估高度造成。
解決辦法:
將estimatedRowHeight、estimatedSectionHeaderHeight、estimatedSectionFooterHeight均設(shè)置為0,即將默認(rèn)開啟關(guān)閉。
UIScrollView
- scrollView在iOS11新增的兩個(gè)屬性:
adjustContentInset和contentInsetAdjustmentBehavior。
adjustContentInset表示contentView.frame.origin偏移了scrollview.frame.origin多少;是系統(tǒng)計(jì)算得來的,計(jì)算方式由contentInsetAdjustmentBehavior決定。有以下幾種枚舉計(jì)算方式:
UIScrollViewContentInsetAdjustmentAutomatic:如果scrollview在一個(gè)automaticallyAdjustsScrollViewContentInset = YES 的controller上,并且這個(gè)Controller包含在一個(gè)Navigation controller中,這種情況下會(huì)設(shè)置在top & bottom上 adjustedContentInset = safeAreaInset + contentInset不管是否滾動(dòng)。其他情況下與UIScrollViewContentInsetAdjustmentScrollableAxes相同UIScrollViewContentInsetAdjustmentScrollableAxes: 在可滾動(dòng)方向上adjustedContentInset = safeAreaInset + contentInset,在不可滾動(dòng)方向上adjustedContentInset = contentInset;依賴于scrollEnabled和alwaysBounceHorizontal / Vertical = YES,scrollEnabled默認(rèn)為YES,所以大多數(shù)情況下,計(jì)算方式還是adjustedContentInset = safeAreaInset + contentInsetUIScrollViewContentInsetAdjustmentNever: 這種方式下adjustedContentInset = contentInsetUIScrollViewContentInsetAdjustmentAlways: 這種方式下會(huì)這么計(jì)算 adjustedContentInset = safeAreaInset + contentInset
當(dāng)contentInsetAdjustmentBehavior設(shè)置為UIScrollViewContentInsetAdjustmentNever的時(shí)候,adjustContentInset值不受SafeAreaInset值的影響。
解決辦法總結(jié)
重新設(shè)置tableView的contentInset值,來抵消掉SafeAreaInset值
因?yàn)閮?nèi)容下移偏移量 = contentInset + SafeAreaInset,如果之前自己設(shè)置了contentInset值為(64,0,0,0),現(xiàn)在系統(tǒng)又設(shè)置了SafeAreaInsets值為(64,0,0,0),那么tableView內(nèi)容下移了64pt,這種情況下,可以設(shè)置contentInset值為(0,0,0,0),也就是遵從系統(tǒng)的設(shè)置了。
設(shè)置tableView的contentInsetAdjustmentBehavior屬性
contentInsetAdjustmentBehavior屬性也是用來取代automaticallyAdjustsScrollViewInsets屬性的,推薦使用這種方式
如果不需要系統(tǒng)為你設(shè)置邊緣距離,可以做以下設(shè)置:
//如果iOS的系統(tǒng)是11.0,會(huì)有這樣一個(gè)宏定義“#define __IPHONE_11_0 110000”;如果系統(tǒng)版本低于11.0則沒有這個(gè)宏定義
#ifdef __IPHONE_11_0
if ([tableView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
#endif
通過設(shè)置iOS 11新增的屬性addtionalSafeAreaInset
iOS 11之前,通過將Controller的automaticallyAdjustsScrollViewInsets屬性設(shè)置為NO,來禁止系統(tǒng)對(duì)tableView調(diào)整contentInsets的。如果還是想從Controller級(jí)別解決問題,那么可以通過設(shè)置Controller的additionalSafeAreaInsets屬性,如果SafeAreaInset值為(20,0,0,0),那么設(shè)置additionalSafeAreaInsets屬性值為(-20,0,0,0),則SafeAreaInsets不會(huì)對(duì)adjustedContentInset值產(chǎn)生影響,tableView內(nèi)容不會(huì)顯示異常。這里需要注意的是addtionalSafeAreaInset是Controller的屬性,要知道SafeAreaInset的值是由哪個(gè)Controller引起的,可能是由自己的Controller調(diào)整的,可能是navigationController調(diào)整的。是由哪個(gè)Controller調(diào)整的,則設(shè)置哪個(gè)Controller的addtionalSafeAreaInset值來抵消掉SafeAreaInset值。
導(dǎo)航適配
- iOS 11增加了大標(biāo)題的顯示,通過UINavigationBar的prefersLargeTitles屬性控制,默認(rèn)是不開啟的??梢院雎圆挥米鲞m配。
可以通過navigationItem的largeTitleDisplayMode屬性控制不同頁面大標(biāo)題的顯示,枚舉如下:
typedef NS_ENUM(NSInteger, UINavigationItemLargeTitleDisplayMode) {
// 默認(rèn)自動(dòng)模式依賴上一個(gè) item 的特性
UINavigationItemLargeTitleDisplayModeAutomatic,
// 針對(duì)當(dāng)前 item 總是啟用大標(biāo)題特性
UINavigationItemLargeTitleDisplayModeAlways,
// Never
UINavigationItemLargeTitleDisplayModeNever,
} NS_SWIFT_NAME(UINavigationItem.LargeTitleDisplayMode);
- Navigation 集成 UISearchController
把你的UISearchController賦值給navigationItem,就可以實(shí)現(xiàn)將UISearchController集成到Navigation。
navigationItem.searchController //iOS 11 新增屬性
navigationItem.hidesSearchBarWhenScrolling //決定滑動(dòng)的時(shí)候是否隱藏搜索框;iOS 11 新增屬性
- UINavigationController和滾動(dòng)交互
滾動(dòng)的時(shí)候,以下交互操作都是由UINavigationController負(fù)責(zé)調(diào)動(dòng)的:
- UIsearchController搜索框效果更新
- 大標(biāo)題效果的控制
- Rubber banding效果 //當(dāng)你開始往下拉,大標(biāo)題會(huì)變大來回應(yīng)那個(gè)滾輪
所以,如果你使用navigation bar,組裝push和pop體驗(yàn),你不會(huì)得到searchController的集成、大標(biāo)題的控制更新和Rubber banding效果,因?yàn)檫@些都是由UINavigationController控制的。
- UIToolbar and UINavigationBar— Layout
在 iOS 11 中,當(dāng)蘋果進(jìn)行所有這些新特性時(shí),也進(jìn)行了其他的優(yōu)化,針對(duì) UIToolbar 和 UINavigaBar 做了新的自動(dòng)布局?jǐn)U展支持,自定義的bar button items、自定義的title都可以通過layout來表示尺寸。 需要注意的是,你的constraints需要在view內(nèi)部設(shè)置,所以如果你有一個(gè)自定義的標(biāo)題視圖,你需要確保任何約束只依賴于標(biāo)題視圖及其任何子視圖。當(dāng)你使用自動(dòng)布局,系統(tǒng)假設(shè)你知道你在做什么。
- Avoiding Zero-Sized Custom Views
自定義視圖的size為0是因?yàn)槟阌幸恍┠:募s束布局。要避免視圖尺寸為0,可以從以下方面做:
UINavigationBar 和 UIToolbar 提供位置
-
開發(fā)者則必須提供視圖的size,有三種方式:
a. 對(duì)寬度和高度的約束;
b. 實(shí)現(xiàn) intrinsicContentSize;
c. 通過約束關(guān)聯(lián)你的子視圖;
導(dǎo)航欄
導(dǎo)航欄高度的變化
iOS11之前導(dǎo)航欄默認(rèn)高度為64pt(statusBar + NavigationBar),iOS11之后如果設(shè)置了prefersLargeTitles = YES則為96pt,默認(rèn)情況下還是64pt,但在iPhoneX上由于劉海的出現(xiàn)statusBar由以前的20pt變成了44pt,所以iPhoneX上高度變?yōu)?8pt,如果項(xiàng)目里隱藏了導(dǎo)航欄加了自定義按鈕之類的,注意適配一下。
導(dǎo)航欄圖層及對(duì)titleView布局的影響
iOS11之前導(dǎo)航欄的title是添加在`UINavigationItemView上面,而navigationBarButton則直接添加在UINavigationBar上面,如果設(shè)置了titleView,則titleView也是直接添加在UINavigationBar上面。


iOS11之后,大概因?yàn)閘argeTitle的原因,視圖層級(jí)發(fā)生了變化,如果沒有給titleView賦值,則titleView會(huì)直接添加在_UINavigationBarContentView上面,如果賦值了titleView,則會(huì)把titleView添加在_UITAMICAdaptorView上,而navigationBarButton被加在了_UIButtonBarStackView上,然后他們都被加在了_UINavigationBarContentView上

如果你的項(xiàng)目是自定義的navigationBar,那么在iOS11上運(yùn)行就可能出現(xiàn)布局錯(cuò)亂的bug,解決辦法是重寫UINavigationBar的layoutSubviews方法,調(diào)整布局:
- (void)layoutSubviews {
[super layoutSubviews];
// 注意導(dǎo)航欄及狀態(tài)欄高度適配
self.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), naviBarHeight);
for (UIView *view in self.subviews) {
if([NSStringFromClass([view class]) containsString:@"Background"]) {
view.frame = self.bounds;
}else if ([NSStringFromClass([view class]) containsString:@"ContentView"]) {
CGRect frame = view.frame;
frame.origin.y = statusBarHeight;
frame.size.height = self.bounds.size.height - frame.origin.y;
view.frame = frame;
}
}
}
titleView支持autolayout,這要求titleView必須是能夠自撐開的或?qū)崿F(xiàn)了- intrinsicContentSize方法:
- (CGSize)intrinsicContentSize {
return UILayoutFittingExpandedSize;
}
TabBarController
主要是tabBar高度及tabBarItem偏移適配,iPhoneX由于底部安全區(qū)的原因UITabBar高度由49pt變成了83pt,可以通過判斷機(jī)型來修改相關(guān)界面代碼:
// UIDevice 分類
- (BOOL)isIPhoneX{
if ([UIScreen instancesRespondToSelector:@selector(currentMode)]) {
return CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size);
}else{
return NO;
}
}
其他適配注意事項(xiàng)
部分項(xiàng)目的輪播圖在iOS 11+Xcode編譯的情況下,會(huì)出現(xiàn)是上下左右任意滾動(dòng)的bug,推薦使用 YJBannerView輪播圖,完全適配iOS 11。Github地址:YJBannerView 使用方法:鏈接