適配 iPhone X 的相關(guān)內(nèi)容詳見(jiàn)我的另一篇文章關(guān)于 SafeArea 和 適配 iPhoneX
前言
又是一波震撼人心的蘋(píng)果秋季新產(chǎn)品發(fā)布會(huì)瘟则,同時(shí)也放出了 OS 的 GM 版诫睬,相信很多果粉已經(jīng)按捺不住內(nèi)心已經(jīng)開(kāi)始了升級(jí)之旅唉堪。當(dāng)然,從 iOS 11 beta1 就已經(jīng)嘗鮮的朋友也不在少數(shù)晨雳。所以撒会,同時(shí)也意味著 iOS 開(kāi)發(fā)者的 iOS11 的適配工作已經(jīng)刻不容緩了。
1蔬啡、適配導(dǎo)航欄(UINavigationBar)
導(dǎo)航欄遇到問(wèn)題
別家 App 早就已經(jīng)適配了 iOS11诲侮,可是原諒我有其他的事情,而且大致在 iOS11 上運(yùn)行了一下 App箱蟆,沒(méi)有發(fā)現(xiàn)什么問(wèn)題沟绪,所以適配工作一直在耽擱,但是近些天更新了 Xcode9 在新的編譯器上編譯運(yùn)行了一下 App顽腾,WTF近零,一眼就看到了下面的丑爆的導(dǎo)航欄,什么鬼抄肖?然后在iOS11以下
的設(shè)備上運(yùn)行久信,完全正常。
我們的 App 的導(dǎo)航欄不是用的UINavigationController
的navigationBar
漓摩,而是每個(gè)VC
都有一個(gè)自己的navigationBar
裙士,這樣的話(huà),導(dǎo)航欄的自由度高管毙,而且個(gè)人感覺(jué)系統(tǒng)原生的導(dǎo)航欄的切換效果不好(當(dāng)然腿椎,iOS11 導(dǎo)航欄的大標(biāo)題切換特效還是蠻可以的桌硫,可以參考音樂(lè)、備忘錄等系統(tǒng)應(yīng)用查看效果啃炸,也可以自己添加設(shè)置UINavigationBar
的屬性setPrefersLargeTitles
來(lái)實(shí)現(xiàn)大標(biāo)題)铆隘。
iOS11 的導(dǎo)航欄:
之前正常的導(dǎo)航欄見(jiàn)下面的 Nav_Normal 截圖
分析問(wèn)題
好的,那就讓我們一探究竟南用,到底是怎么回事膀钠。
經(jīng)過(guò) DebugView,在下圖看出了端倪裹虫。
原來(lái) iOS11 的
UINavigationBar
有兩個(gè)子視圖肿嘲,分別是UIBarBackground
和UINavigationBarContentView
,我們重點(diǎn)放在后者身上筑公,因?yàn)?code>BarButtonItem和TitleLabel
都被添加到他上面雳窟,可以仔細(xì)看一下上面這張截圖,可以很清楚看出各自之間的關(guān)系匣屡。
知道了圖層之間的關(guān)系封救,那為什么我們的導(dǎo)航欄會(huì)在 iOS11 上產(chǎn)生出如上的元素錯(cuò)亂的問(wèn)題呢?仔細(xì)觀察會(huì)發(fā)現(xiàn)捣作,貌似是往上平移了將近 20 px兴泥,我們接著看一下布局,見(jiàn)下圖
果然
UINavigationBarContentView
的midY坐標(biāo)是22虾宇,而這個(gè) contentView 的高度是 44px搓彻,所以到這里已經(jīng)找到了原因,原來(lái)是直接忽略狀態(tài)欄的 20px 直接貼在了屏幕頂部嘱朽,所以布局不錯(cuò)亂才怪旭贬。
解決方案
找到了原因,我們距離成功就不遠(yuǎn)了搪泳,創(chuàng)建一個(gè)UINavigationBar
的子類(lèi)稀轨,重寫(xiě)layoutSubviews
,重新布局導(dǎo)航欄的子視圖岸军,如下
- (void)layoutSubviews {
[super layoutSubviews];
self.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), 64);
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 = 20;
frame.size.height = self.bounds.size.height - frame.origin.y;
view.frame = frame;
}
}
}
編譯運(yùn)行一下奋刽,完美解決。
Debug一下艰赞,布局已經(jīng)正確了
導(dǎo)航欄適配總結(jié)
也有一些童鞋也到了titleView
布局錯(cuò)亂問(wèn)題佣谐,iOS10 及以下自定義titleView
會(huì)添加在navigationBar
上,而 iOS11 添加在UINavigationBarContentView
上方妖,之前的UINavigationItemView
在 iOS11 替換成了UINavigationBarContentView
狭魂。使用上面的解決思路應(yīng)該很快就能解決。
為了實(shí)現(xiàn) iOS11 新系統(tǒng)的大標(biāo)題新UI效果,蘋(píng)果部分重構(gòu)了UINavigationBar
的代碼及元素布局邏輯雌澄,從 iOS7 到 iOS10 階段內(nèi)導(dǎo)航欄都沒(méi)有大的變動(dòng)斋泄,而這個(gè)
iOS11 帶來(lái)的變動(dòng)或多或少會(huì)影響到我們的 App 導(dǎo)航欄的布局效果。而且在[Apple Developer Forums]也有相關(guān)的討論镐牺,像sizeThatFits not working炫掐,像There has a problem with UINavigationbar(maybe is bug),可能在這部分代碼蘋(píng)果還會(huì)相繼修改完善睬涧。但不管怎么說(shuō)卒废,現(xiàn)階段還是應(yīng)該找一套可行的適配方案來(lái)應(yīng)對(duì)導(dǎo)航欄變化帶來(lái)的影響。
2宙地、適配 UITableViewController(UIScrollView)
ScrollView 自動(dòng)適配 Insets 的方式變化automaticallyAdjustsScrollViewInsets
與contentInsetAdjustmentBehavior
-
automaticallyAdjustsScrollViewInsets
是控制器ViewController
的屬性,默認(rèn)是true
逆皮,一般來(lái)說(shuō)如果ScrollView
直接添加在控制器視圖上時(shí)宅粥,會(huì)自動(dòng)設(shè)置ScrollView
的Inset
屬性來(lái)空出status bar, search bar, navigation bar, toolbar, or tab bar
的位置來(lái)。在日常開(kāi)發(fā)中為了程序的穩(wěn)定性與自由度一般我們是將這個(gè)屬性設(shè)置為false
电谣。但是秽梅,這個(gè)屬性在iOS11
失效了,所以導(dǎo)致了我們 App 的下面的問(wèn)題剿牺。
問(wèn)題很好解決企垦,在文檔中標(biāo)注很明白
@property(nonatomic,assign) BOOL automaticallyAdjustsScrollViewInsets API_DEPRECATED_WITH_REPLACEMENT("Use UIScrollView's contentInsetAdjustmentBehavior instead", ios(7.0,11.0),tvos(7.0,11.0)); // Defaults to YES
-
contentInsetAdjustmentBehavior
對(duì)于我們現(xiàn)在來(lái)說(shuō)是個(gè)陌生面孔。這是在 iOS11 為ScrollView
新定義的一個(gè)枚舉屬性晒来。注意钞诡,上面談到的automaticallyAdjustsScrollViewInsets
是控制器的屬性。
typedef NS_ENUM(NSInteger, UIScrollViewContentInsetAdjustmentBehavior) {
UIScrollViewContentInsetAdjustmentAutomatic, // Similar to .scrollableAxes, but for backward compatibility will also adjust the top & bottom contentInset when the scroll view is owned by a view controller with automaticallyAdjustsScrollViewInsets = YES inside a navigation controller, regardless of whether the scroll view is scrollable
UIScrollViewContentInsetAdjustmentScrollableAxes, // Edges for scrollable axes are adjusted (i.e., contentSize.width/height > frame.size.width/height or alwaysBounceHorizontal/Vertical = YES)
UIScrollViewContentInsetAdjustmentNever, // contentInset is not adjusted
UIScrollViewContentInsetAdjustmentAlways, // contentInset is always adjusted by the scroll view's safeAreaInsets
} API_AVAILABLE(ios(11.0),tvos(11.0));
所以我們將這個(gè)屬性設(shè)置為.AdjustmentNever
即可解決Insets
異常問(wèn)題湃崩。
if (@available(iOS 11.0, *)) {
aboutMeTableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
如下圖
- 每個(gè)需要修改的地方都寫(xiě)令人討厭的
ifelse
代碼有些不爽荧降,為了方面,我寫(xiě)了一個(gè)宏定義攒读,如下朵诫,設(shè)置起來(lái)就簡(jiǎn)單過(guò)了
MCDisbaleAutoAdjustScrollViewInsets(_tableView, self)
/**
取消自動(dòng)適配 ScrollView 的 Insets 行為
@param scrollView 滑動(dòng)視圖
@param vc 所在控制器
*/
#define MCDisbaleAutoAdjustScrollViewInsets(scrollView, vc)\
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \
if (@available(iOS 11.0,*)) {\
scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;\
} else {\
vc.automaticallyAdjustsScrollViewInsets = NO;\
}\
_Pragma("clang diagnostic pop")\
} while (0);
預(yù)估高度estimatedXXHeight
iOS11 中的estimatedXXHeight
由默認(rèn)的 0 變成了現(xiàn)在的默認(rèn).AutomaticDimension
,導(dǎo)致高度計(jì)算出錯(cuò)薄扁,最后導(dǎo)致的現(xiàn)象就是上拉加載更多的時(shí)候 UI 錯(cuò)亂剪返、TableView
視圖的高度異常等一系列問(wèn)題。重新置 0 即可邓梅。
_tableView.estimatedRowHeight = 0;
_tableView.estimatedSectionFooterHeight = 0;
_tableView.estimatedSectionHeaderHeight = 0;
3脱盲、其他
-
info.plist
新添Privacy - Photo Library Additions Usage Description
鍵,跟Privacy - Photo Library Usage Description
的不同之處在于日缨,前者允許你只寫(xiě)入圖庫(kù)宾毒,不需要讀取。這樣使得隱私權(quán)限更加細(xì)化。對(duì)這個(gè)鍵官方描述如下:Although this keys governs read and write access to the user’s photo library, it’s best to use NSPhotoLibraryAddUsageDescription if your app needs only to add assets to the library and does not need to read any assets.