前言
蘋果WWDC開(kāi)發(fā)者大會(huì)上,終于發(fā)布了大家期待已久的iOS 11,有些新特性功能確實(shí)出人意料。不過(guò)大的方面蘋果貌似也就 AR 和 GM 機(jī)器學(xué)習(xí)了,9月13日凌晨1點(diǎn),蘋果開(kāi)了新品發(fā)布會(huì),相信大家都已經(jīng)知道Phone X 的劉海了,看起來(lái)不是很雅觀,對(duì)于iOS開(kāi)發(fā)者來(lái)說(shuō),適配工作也帶來(lái)了麻煩,iOS11在新舊API 方面做了新的改動(dòng),未來(lái)App Store就會(huì)出現(xiàn)很多大量的APP更新,針對(duì)iOS11和iPhone X的適配。
存在的適配問(wèn)題
1、啟動(dòng)頁(yè)的適配
相信一部分開(kāi)發(fā)者已經(jīng)在著手適配iPhone X 和iOS11 了,xcode9測(cè)試版運(yùn)行自己的項(xiàng)目會(huì)發(fā)現(xiàn)項(xiàng)目沒(méi)有充滿屏幕,上下會(huì)有黑色區(qū)域的情況,大家別慌,這是沒(méi)有設(shè)置對(duì)應(yīng)的啟動(dòng)圖,iPhone X對(duì)應(yīng)像素 1125 * 2436
(1)使用LaunchImage
如果你使用的是LauchImage加載的啟動(dòng)頁(yè),那么對(duì)于他的適配就比較簡(jiǎn)單了,直接在LauchImage中添加一個(gè)1125 * 2436的啟動(dòng)圖片啟動(dòng)頁(yè)面即可。
大家可以自己添加圖片或者準(zhǔn)備一張尺寸:1125 * 2436的啟動(dòng)圖片, 移動(dòng)到LaunchImage的Finder目錄中, 并在LaunchImage中的Contents.json文件中增加 (注意Json格式應(yīng)該無(wú)需手動(dòng)添加,默認(rèn)應(yīng)該會(huì)自動(dòng)添加的,未嘗試):
{
"extent" : "full-screen",
"idiom" : "iphone",
"subtype" : "2436h",
"filename" : "圖片名字.png",
"minimum-system-version" : "11.0",
"orientation" : "portrait",
"scale" : "3x"
}
(2)Launch Screen Storyboard
如果你使用的是Launch Screen Storyboard 方式來(lái)添加的啟動(dòng)頁(yè),當(dāng)然對(duì)于非iphoneX的約束你無(wú)需考慮,不過(guò)iPhone X 的狀態(tài)欄由原來(lái)的 20 變?yōu)榱?44。這個(gè)如果在導(dǎo)航的位置設(shè)置自定義的 View,在 iPhone X 上出問(wèn)題。會(huì)擋住 View 的顯示。
所以你在自定義啟動(dòng)頁(yè)的時(shí)候需要專門針對(duì)iphonX做對(duì)應(yīng)的配置,需要調(diào)整下 Top 的約束,以前為 -20 ,改為 -44 ;
<font color='red'>所以我更建議大家使用LauchImage加載的啟動(dòng)頁(yè),這樣比較方便快捷,可以直接針對(duì)iphoneX設(shè)置對(duì)應(yīng)的啟動(dòng)頁(yè),當(dāng)然如果你需要自定義啟動(dòng)頁(yè)的動(dòng)畫效果什么的,還是比較適合使用Launch Screen Storyboard。</font>
2、UITabelView、UIScrollView的適配問(wèn)題
我看了多篇文章,對(duì)UITabelView、UIScrollView的適配方式,不盡相同,但在對(duì)UITabelView、UIScrollView適配之前,我們首先要了解一下iOS11引入的一個(gè)新的概念:Safe Area;
什么是Safe Area,我的理解就因iphoneX的曲屏導(dǎo)致了某些區(qū)域無(wú)法用于執(zhí)行用戶的交互,開(kāi)發(fā)者就只能將用戶的交互事件及頁(yè)面展示于那些除了圓角區(qū)域的可交互區(qū)域范圍內(nèi),這樣就能保證開(kāi)發(fā)者設(shè)計(jì)的app能夠正常的使用與交互,但如果用戶使用了系統(tǒng)的navigationbar以及系統(tǒng)的uitabbar,那么系統(tǒng)就會(huì)默認(rèn)把安全區(qū)域縮減為去掉navigationbar以及uitabbar默認(rèn)高度的用戶可操作區(qū)域,這就是我認(rèn)為的安全區(qū)域。具體的理解我建議查看這兩篇文章:三分鐘弄懂iPhone X 設(shè)計(jì)尺寸和適配以及iOS 11 安全區(qū)域適配總結(jié)。
(1)UIScrollView的適配問(wèn)題
對(duì)于UIScrollView的iOS11適配問(wèn)題,無(wú)非就是針對(duì)safe area所進(jìn)行的適配。
首先我先來(lái)說(shuō)一下什么情況下需要適配當(dāng)tableView的frame超出安全區(qū)域范圍時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)整內(nèi)容的位置,SafeAreaInsets值會(huì)不為0,于是影響tableView的adjustContentInset值,進(jìn)而影響tableView的內(nèi)容展示,導(dǎo)致tableView的content下移了SafeAreaInsets的距離。SafeAreaInsets值為0時(shí),是正常的情況。
我們需要了解每個(gè)頁(yè)面的結(jié)構(gòu),看tableView是否被系統(tǒng)的statusbar或navigationbar覆蓋,如果被覆蓋的話,則會(huì)發(fā)生下移。也可以通過(guò)tableview.safeAreaInsets的值來(lái)確認(rèn)是因?yàn)榘踩珔^(qū)域的問(wèn)題導(dǎo)致的內(nèi)容下移。
如下代碼片段,可以看出系統(tǒng)對(duì)tableView向下調(diào)整了20pt的距離,因?yàn)閠ableView超出了安全區(qū)域范圍,被statusbar覆蓋。
那么我們就來(lái)看一下,如何解決這個(gè)問(wèn)題:
-
新增contentInsetAdjustmentBehavior屬性
iOS11 廢棄了之前UIViewController的automaticallyAdjustsScrollViewInsets,使用contentInsetAdjustmentBehavior屬性進(jìn)行了替代,作用與先前的類似,根據(jù)某些情況自動(dòng)調(diào)整scrollview的contentInset(實(shí)際改變的是adjustedContentInset屬性,contentInset屬性不會(huì)變)
adjustContentInset表示contentView.frame.origin偏移了scrollview.frame.origin多少;是系統(tǒng)計(jì)算得來(lái)的,計(jì)算方式由contentInsetAdjustmentBehavior決定。有以下幾種計(jì)算方式,即contentInsetAdjustmentBehavior的四個(gè)枚舉類型:
- 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 + contentInset;
- UIScrollViewContentInsetAdjustmentNever: adjustedContentInset = contentInset
- UIScrollViewContentInsetAdjustmentAlways: adjustedContentInset = safeAreaInset + contentInset
當(dāng)contentInsetAdjustmentBehavior設(shè)置為UIScrollViewContentInsetAdjustmentNever的時(shí)候,adjustContentInset值不受SafeAreaInset值的影響。
(2)UITableView
- 在UITableview中的解決偏移的問(wèn)題
1.重新設(shè)置tableView的contentInset值,來(lái)抵消掉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è)置了。
2.設(shè)置tableView的contentInsetAdjustmentBehavior屬性
如果不需要系統(tǒng)為你設(shè)置邊緣距離,可以做以下設(shè)置:
//如果iOS的系統(tǒng)是11.0,會(huì)有這樣一個(gè)宏定義“#define __IPHONE_11_0 110000”;
如果系統(tǒng)版本低于11.0則沒(méi)有這個(gè)宏定義
#ifdef __IPHONE_11_0
if ([tableView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
#endif
contentInsetAdjustmentBehavior屬性也是用來(lái)取代automaticallyAdjustsScrollViewInsets屬性的,推薦使用這種方式。
3.通過(guò)設(shè)置iOS 11新增的屬性addtionalSafeAreaInset;
iOS 11之前,大家是通過(guò)將Controller的automaticallyAdjustsScrollViewInsets屬性設(shè)置為NO,來(lái)禁止系統(tǒng)對(duì)tableView調(diào)整contentInsets的。如果還是想從Controller級(jí)別解決問(wèn)題,那么可以通過(guò)設(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值來(lái)抵消掉SafeAreaInset值。
<font color='red'>關(guān)于MJRefresh</font>
同時(shí),由于contentInsetAdjustmentBehavior的產(chǎn)生以及automaticallyAdjustsScrollViewInsets的廢棄,我們經(jīng)常用的MJRefresh也會(huì)產(chǎn)生相應(yīng)的問(wèn)題,具體的解決方法,與之同理。
- iOS 11中UITableview默認(rèn)啟用Self-Sizing
這個(gè)應(yīng)該是UITableView最大的改變。我們知道在iOS8引入Self-Sizing 之后,我們可以通過(guò)實(shí)現(xiàn)estimatedRowHeight相關(guān)的屬性來(lái)展示動(dòng)態(tài)的內(nèi)容,實(shí)現(xiàn)了estimatedRowHeight屬性后,得到的初始contenSize是個(gè)估算值,是通過(guò)estimatedRowHeight x cell的個(gè)數(shù)得到的,并不是最終的contenSize,tableView就不會(huì)一次性計(jì)算所有的cell的高度了,只會(huì)計(jì)算當(dāng)前屏幕能夠顯示的cell個(gè)數(shù)再加上幾個(gè),滑動(dòng)時(shí),tableView不停地得到新的cell,更新自己的contenSize,在滑到最后的時(shí)候,會(huì)得到正確的contenSize。在測(cè)試Demo中,創(chuàng)建tableView到顯示出來(lái)的過(guò)程中,contentSize的計(jì)算過(guò)程如下圖:

Self-Sizing在iOS11下是默認(rèn)開(kāi)啟的,Headers, footers, and cells都默認(rèn)開(kāi)啟Self-Sizing,所有estimated 高度默認(rèn)值從iOS11之前的 0 改變?yōu)閁ITableViewAutomaticDimension:

如果目前項(xiàng)目中沒(méi)有使用estimateRowHeight屬性,在iOS11的環(huán)境下就要注意了,因?yàn)殚_(kāi)啟Self-Sizing之后,tableView是使用estimateRowHeight屬性的,這樣就會(huì)造成contentSize和contentOffset值的變化,如果是有動(dòng)畫是觀察這兩個(gè)屬性的變化進(jìn)行的,就會(huì)造成動(dòng)畫的異常,因?yàn)樵诠浪阈懈邫C(jī)制下,contentSize的值是一點(diǎn)點(diǎn)地變化更新的,所有cell顯示完后才是最終的contentSize值。因?yàn)椴粫?huì)緩存正確的行高,tableView reloadData的時(shí)候,會(huì)重新計(jì)算contentSize,就有可能會(huì)引起contentOffset的變化。iOS11下不想使用Self-Sizing的話,可以通過(guò)以下方式關(guān)閉:(前言中提到的問(wèn)題也是通過(guò)這種方式解決的)

iOS11下,如果沒(méi)有設(shè)置estimateRowHeight的值,也沒(méi)有設(shè)置rowHeight的值,那contentSize計(jì)算初始值是 44 * cell的個(gè)數(shù),如下圖:rowHeight和estimateRowHeight都是默認(rèn)值UITableViewAutomaticDimension 而rowNum = 15;則初始contentSize = 44 * 15 = 660;

- 新增separatorInsetReference屬性
分割線相關(guān)的,有2個(gè)可選值:
public enum UITableViewSeparatorInsetReference : Int {
// The value set to the separatorInset property is interpreted as an offset from the edges of the cell.
case fromCellEdges
// The value set to the separatorInset property is interpreted as an offset from the automatic separator insets.
case fromAutomaticInsets
}
舉個(gè)例子,TableView的separator默認(rèn)左邊會(huì)留15,如果要去掉這個(gè)空隙,頂頭顯示
iOS 11之前的寫法:
table.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
cell.layoutMargins = .zero
iOS 11之后的寫法:
table.separatorInsetReference = .fromCellEdges //默認(rèn)就是fromCellEdges,所以可以不寫這行代碼
cell.separatorInset = .zero
-
Swipe actions
在iOS8之后,蘋果官方增加了UITableVIew的右滑操作接口,即新增了一個(gè)代理方法(tableView: editActionsForRowAtIndexPath:)和一個(gè)類(UITableViewRowAction),代理方法返回的是一個(gè)數(shù)組,我們可以在這個(gè)代理方法中定義所需要的操作按鈕(刪除、置頂?shù)?,這些按鈕的類就是UITableViewRowAction。這個(gè)類只能定義按鈕的顯示文字、背景色、和按鈕事件。并且返回?cái)?shù)組的第一個(gè)元素在UITableViewCell的最右側(cè)顯示,最后一個(gè)元素在最左側(cè)顯示。從iOS 11開(kāi)始有了一些改變,首先是可以給這些按鈕添加圖片了,然后是如果實(shí)現(xiàn)了以下兩個(gè)iOS 11新增的代理方法,將會(huì)取代(tableView: editActionsForRowAtIndexPath:)代理方法:
// Swipe actions
// These methods supersede -editActionsForRowAtIndexPath: if implemented
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
這兩個(gè)代理方法返回的是UISwipeActionsConfiguration類型的對(duì)象,創(chuàng)建該對(duì)象及賦值可看下面的代碼片段:
- ( UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
//刪除
UIContextualAction *deleteRowAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:@"delete" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
[self.titleArr removeObjectAtIndex:indexPath.row];
completionHandler (YES);
}];
deleteRowAction.image = [UIImage imageNamed:@"icon_del"];
deleteRowAction.backgroundColor = [UIColor blueColor];
UISwipeActionsConfiguration *config = [UISwipeActionsConfiguration configurationWithActions:@[deleteRowAction]];
return config;
}
創(chuàng)建UIContextualAction對(duì)象時(shí),UIContextualActionStyle有兩種類型,如果是置頂、已讀等按鈕就使用UIContextualActionStyleNormal類型,delete操作按鈕可使用UIContextualActionStyleDestructive類型,當(dāng)使用該類型時(shí),如果是右滑操作,一直向右滑動(dòng)某個(gè)cell,會(huì)直接執(zhí)行刪除操作,不用再點(diǎn)擊刪除按鈕,這也是一個(gè)好玩的更新。
滑動(dòng)操作這里還有一個(gè)需要注意的是,當(dāng)cell高度較小時(shí),會(huì)只顯示image,不顯示title,當(dāng)cell高度夠大時(shí),會(huì)同時(shí)顯示image和title。我寫demo測(cè)試的時(shí)候,因?yàn)槊總€(gè)cell的高度都較小,所以只顯示image,然后我增加cell的高度后,就可以同時(shí)顯示image和title了。見(jiàn)下圖對(duì)比:

3、在UIKit’s Bars中加入的新功能
-
控制大標(biāo)題的顯示
在UI navigation bar中新增了一個(gè)BOOL屬性prefersLargeTitles,將該屬性設(shè)置為ture,navigation bar就會(huì)在整個(gè)APP中顯示大標(biāo)題,如果想要在控制不同頁(yè)面大標(biāo)題的顯示,可以通過(guò)設(shè)置當(dāng)前頁(yè)面的navigationItem的largeTitleDisplayMode屬性;
大標(biāo)題,默認(rèn)為false,當(dāng)設(shè)置為true時(shí),navigation bar會(huì)顯示大標(biāo)題,向上滑動(dòng)頁(yè)面,navigation bar 會(huì)變小直到顯示成跟之前一樣,同時(shí)title位置會(huì)發(fā)生變化,變大后的樣式,如圖所示:
bigTitle 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都可以通過(guò)layout來(lái)表示尺寸。 需要注意的是,你的constraints需要在view內(nèi)部設(shè)置,所以如果你有一個(gè)自定義的標(biāo)題視圖,你需要確保任何約束只依賴于標(biāo)題視圖及其任何子視圖。當(dāng)你使用自動(dòng)布局,系統(tǒng)假設(shè)你知道你在做什么。具體的實(shí)例,可以查看簡(jiǎn)書app的適配問(wèn)題;-
Navigation 集成 UISearchController
把你的UISearchController賦值給navigationItem,就可以實(shí)現(xiàn)將UISearchController集成到Navigation。
searchBar
swift代碼如下:
let searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.backgroundColor = .white
navigationItem.searchController = searchController
-
UIBarItem
在iPhone上,tab上的圖標(biāo)較小,tab bar較小,這樣垂直空間可多放置內(nèi)容。如果有人看不清楚tab bar上的圖標(biāo)或文字,可以通過(guò)長(zhǎng)按tab bar上的任意item,會(huì)將該item顯示在HUD上,這樣可以清楚的看清icon和text。對(duì)tool bar 和 navigation bar同理,長(zhǎng)按item也會(huì)放大顯示。如下圖顯示:

UIBarItem是UI tab bar item和UI bar button item的父類,要想實(shí)現(xiàn)上面介紹的效果,只需要為UIBarItem 設(shè)置landscapeImagePhone屬性,在storyboard中也支持這個(gè)設(shè)置,對(duì)于HUD的image需要設(shè)置另一個(gè)iOS11新增的屬性:largeContentSizeImage,關(guān)于這部分更詳細(xì)的討論,可以參考 WWDC2017 Session 215:What's New in Accessibility
4、applicationDidEnterBackground 不執(zhí)行
APP 進(jìn)入后臺(tái)后,applicationDidEnterBackground: 這個(gè)方法將延遲大約 1000 毫秒執(zhí)行, 那么如果在進(jìn)入后臺(tái)時(shí)做一些任務(wù),可能會(huì)達(dá)不到預(yù)期的效果。如果 APP 剛進(jìn)入應(yīng)用立即啟動(dòng),applicationDidEnterBackground: 和 applicationWillEnterForeground: 這兩個(gè)方法都不會(huì)調(diào)用。如果有這么一個(gè)場(chǎng)景,進(jìn)入后臺(tái)后給應(yīng)用設(shè)置手勢(shì)密碼,當(dāng) APP 剛進(jìn)入后就立即啟動(dòng),那么 applicationDidEnterBackground:這個(gè)方法不會(huì)立即執(zhí)行,從而手勢(shì)密碼也就不會(huì)設(shè)置。
5.UIImagePickerController 設(shè)置導(dǎo)航背景圖
[self.navigationBar setBackgroundImage:[UIImage imageNamed:@"navBg"] forBarMetrics:UIBarMetricsDefault];
這樣設(shè)置發(fā)現(xiàn)對(duì) UIImagePickerController 不起作用,需要使用:
self.navigationBar.barTintColor = [UIColor blackColor];
參考文檔
- http://wetest.qq.com/lab/view/326.html
- http://www.itdecent.cn/p/d4a17c32abdf
- https://juejin.im/entry/59c331606fb9a00a58318542
這篇文章是我看了多篇文章之后總結(jié)出來(lái)的,雖然不是很全面,我已經(jīng)盡量做到全面了,如果大家有什么要補(bǔ)充的,歡迎留言評(píng)論,最后我想說(shuō)簡(jiǎn)書的那個(gè)大佬,你寫文章的時(shí)候你女票給你喂水果,要不要這么秀,你讓我們這些單身狗怎么活?。?!

