iOS 11和iPhone X相較于之前的系統(tǒng)和手機(jī)都有了很大的變化,特別是iPhone X在UI上的變化。在iOS 11發(fā)布后,更新系統(tǒng)看了下App,果然是很多地方都存在著異常和Bug,下面針對(duì)已經(jīng)了解和出現(xiàn)的問題進(jìn)行適配說明。
一、iPhone X適配
1、SafeArea
iOS 11之后廢棄了iOS 7在UIViewController中引入的topLayoutGuide和bottomLayoutGuide,開始引入了一個(gè)新的布局概念:SafeArea,它被用來描述視圖中不可被任何內(nèi)容遮擋的區(qū)域。只要我們的UI元素布局在了SafeArea內(nèi),就能避免被NavigationBar、TabBar、StatusBar等等一些bar的遮擋。
iPhone X的安全區(qū)域在默認(rèn)情況下:
- 豎屏 frame : {0, 44, ScreenWidth, ScreenHeight - 44 - 34}
- 橫屏 frame : {44, 0, ScreenWith - 44 - 44, ScreenHeight - 21}
具體顯示如下圖藍(lán)框frame所示:
非iPhone X的話,安全區(qū)域在默認(rèn)情況下:
- 豎屏 frame : {0, 20, ScreenWidth, ScreenHeight - 20}
- 橫屏 frame : {0, 0, ScreenWith, ScreenHeight}
App的布局應(yīng)該在填滿整個(gè)顯示屏的同時(shí)保證內(nèi)容和控件的正確顯示,并且便于點(diǎn)按。我們App的內(nèi)容元素和按鈕要放在SafeArea內(nèi),以避開屏幕角落和傳感器槽,讓其在填滿屏幕的同時(shí)而不被切割而顯示不完整。在橫屏模式中,我們更要注意內(nèi)容和按鈕的布局,如下圖所示。
2、iPhone X
iPhone X最大的變化是屏幕,屏幕采用了高分辨率的圓角全面屏,屏幕尺寸為1125px × 2436px(375pt × 812pt @3x),狀態(tài)欄和頂部tabBar的高度也發(fā)生了變化。
這些變化造成了以下幾個(gè)問題:
2.1、啟動(dòng)iPhone X后屏幕沒鋪滿(上下各有一截黑條)
- 問題原因:項(xiàng)目使用Launch Images Sourc作為啟動(dòng)方式,缺少iPhone X的啟動(dòng)圖
-
解決方案:
方案一:準(zhǔn)備一張1125px × 2436px的啟動(dòng)圖放到項(xiàng)目的LaunchImage里邊
方案二:通過LaunchScreen.storyboard方式啟動(dòng)
2.2、控制器的view大小計(jì)算錯(cuò)誤
- 問題原因:iPhone X的StatusBar和底部TabBar高度都發(fā)生了變化,如果項(xiàng)目中之前計(jì)算view的frame時(shí)寫死了高度(StatusBar : 20, TabBar : 49)的話,就會(huì)出現(xiàn)view計(jì)算出來的frame存在問題。
- 解決方案:抽取出幾個(gè)宏定義來表示StatusBar和TabBar的高度,使用起來也會(huì)非常方便,之后如果再出現(xiàn)改變,變動(dòng)起來也比較靈活。
#define XXStatusBarHeight [[UIApplication sharedApplication] statusBarFrame].size.height
#define XXNavBarHeight 44.0
#define XXTabBarHeight (XXStatusBarHeight > 20 ? 83 : 49)
#define XXCustomTabBarHeight 49
#define XXTopHeight (XXStatusBarHeight + XXNavBarHeight)
二、iOS 11適配
1、UIScrollView和UITableView新屬性
iOS 11之后,UIViewController的automaticallyAdjustsScrollViewInsets屬性被廢棄了,這個(gè)屬性的作用就是根據(jù)所在界面的StatusBar、NavigationBar和TabBar的高度,自動(dòng)的去調(diào)整UIScrollView的Insets,默認(rèn)為YES。如果設(shè)置為NO,就是由我們自己來修改布局,不讓它自動(dòng)調(diào)整。官方文檔建議我們使用contentInsetAdjustmentBehavior來代替它,這是一個(gè)枚舉屬性,定義如下:
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));
// 通過上面的枚舉,配置 adjustedContentInset 的行為
@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior;
// 如果 contentInsetAdjustmentBehavior 允許,此屬性可以整合 safeAreaInsets 來描述安全區(qū)域
@property(nonatomic, readonly) UIEdgeInsets adjustedContentInset;
// 改變 adjustedContentInset 的 response 和 delegate
- (void)adjustedContentInsetDidChange;
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView;
// 用于描述未經(jīng)轉(zhuǎn)換的 content area 區(qū)域
@property(nonatomic,readonly,strong) UILayoutGuide *contentLayoutGuide;
// 用于描述未經(jīng)轉(zhuǎn)換的 scroll view 的 frame
@property(nonatomic,readonly,strong) UILayoutGuide *frameLayoutGuide;
這個(gè)contentInsetAdjustmentBehavior屬性是用來配置UIScrollView和UITableView的adjustedContentInset的行為的,adjustedContentInset也是iOS 11新增的一個(gè)屬性。當(dāng)contentInsetAdjustmentBehavior允許時(shí),adjustedContentInset這個(gè)屬性替代了contentInset所描述的區(qū)域,并且整合了safeAreaInsets來描述安全區(qū)域。adjustedContentInset表示contentView.frame.origin偏移了scrollView.frame.origin多少。
2、iOS 11
上面所說的iOS 11變化還有其他一些變化,也帶來了一些問題,需要我們?nèi)プ鲞m配。
2.1、UITableView或UIScrollView內(nèi)容發(fā)生向下偏移
- 問題原因: automaticallyAdjustsScrollViewInsets屬性被廢棄后,當(dāng)tableview或scrollview超出安全區(qū)域時(shí),系統(tǒng)自動(dòng)調(diào)整了SafeAreaInsets的值,進(jìn)而影響了adjustedContentInset的值,最終導(dǎo)致tableview或scrollview的內(nèi)容到邊緣的距離發(fā)生了變化,導(dǎo)致內(nèi)容發(fā)生了偏移。
- 解決方案:將contentInsetAdjustmentBehavior屬性置為UIScrollViewContentInsetAdjustmentNever。
if (@available(iOS 11.0, *)) {
_tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
2.2、導(dǎo)航欄返回時(shí)View出現(xiàn)下沉現(xiàn)象
導(dǎo)航欄在返回到上一頁(yè)時(shí),上一頁(yè)的View出現(xiàn)了下沉現(xiàn)象,返回的整個(gè)過程中由下往上浮動(dòng)上來。
- 問題原因:這是因?yàn)閁IScrollView的contentInsetAdjustmentBehavior屬性默認(rèn)為UIScrollViewContentInsetAdjustmentAutomatic,會(huì)對(duì)視圖內(nèi)容的位置做出自動(dòng)計(jì)算和調(diào)整。
- 解決方案:解決方法同上面一致,將UIScrollView的contentInsetAdjustmentBehavior屬性置為UIScrollViewContentInsetAdjustmentNever。
if (@available(iOS 11.0, *)) {
_scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
2.3、UITableView的Header、Footer、cell高度出現(xiàn)問題,上拉刷新cell發(fā)生跳動(dòng)
- 問題原因:UITableView在iOS 11中的明顯改變就是默認(rèn)開啟Self-Sizing,從iOS 8引入Self-Sizing后,可以通過實(shí)現(xiàn)estimatedRowHeight屬性來展示動(dòng)態(tài)的內(nèi)容,但在iOS 11之前都是默認(rèn)關(guān)閉的。當(dāng)開啟時(shí),UITableView的Header,F(xiàn)ooter和cell的高度由原來關(guān)閉時(shí)的0變?yōu)閁ITableViewAutomaticDimension。
- 解決方案:如果項(xiàng)目中沒有用到estimatedRowHeight,但是仍然想使用Self-Sizing關(guān)閉時(shí)的效果,可以使用下面的代碼將Self-Sizing關(guān)閉。
_tableView.estimatedRowHeight = 0;
_tableView.estimatedSectionHeaderHeight = 0;
_tableView.estimatedSectionFooterHeight = 0;
如果不想開啟Self-Sizing效果,或者UITableView的Header、Footer、cell高度出現(xiàn)問題,可以在Appdelegate里邊全局設(shè)置上面提到的這些屬性,能夠避免在單個(gè)類文件中每次都要添加這些代碼所帶來的麻煩,代碼如下:
if (@available(iOS 11.0, *)) {
[UIScrollView appearance].contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
[UITableView appearance].estimatedRowHeight = 0;
[UITableView appearance].estimatedSectionHeaderHeight = 0;
[UITableView appearance].estimatedSectionFooterHeight = 0;
}
2.4、導(dǎo)航欄的左右UIBarButtonItem的UI位置調(diào)整失效
- 問題原因iOS 11之前采用UIBarButtonSystemItemFixedSpace的方式可以對(duì)UIBarButtonItem做一個(gè)位置調(diào)整,但是這種方法在iOS 11已經(jīng)失效,代碼如下所示
UIButton *allBtn = [UIButton buttonWithType:UIButtonTypeCustom];
UIBarButtonItem *rightBarItem = [[UIBarButtonItem alloc] initWithCustomView:allBtn];
UIBarButtonItem *negativeSpacer1 = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
target:nil action:nil];
negativeSpacer1.width = -22;
self.navigationItem.rightBarButtonItems = @[negativeSpacer1,rightBarItem];
并且UINavigationBar的層級(jí)關(guān)系也發(fā)生了變化,如圖所示,上面的是iOS 11之前的NavigationBar層級(jí)關(guān)系,下邊的是iOS 11之后的層級(jí)關(guān)系。iOS 11之后UIBarButtonItem都被放在了_UINavigationBarContentView
--> _UIButtonBarStackView
--> _UITAMICAdaptorView
中了。
- 解決方案:目前沒有找到特別完美的解決方案,臨時(shí)替代方案是,設(shè)置自定義Button的contentHorizontalAlignment屬性,讓Button的內(nèi)容分貝根據(jù)leftBarButtonItem,rightBarButtonItem而左對(duì)齊和右對(duì)齊,從而達(dá)到和之前相似的效果。
if (IOS11) leftButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
if (IOS11) rightButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
2.5、相冊(cè)權(quán)限獲取
在iOS 11之前,我們需要在info.plist中添加Privacy - Photo Library Usage Description
,用戶在訪問相冊(cè)(包括讀和寫權(quán)限)的時(shí)候,才能夠彈出授權(quán)。iOS 11之后:
-
Privacy - Photo Library Usage Description
無(wú)需添加,默認(rèn)就賦予用戶了相冊(cè)的讀權(quán)限,但是為了適配iOS 11之前的系統(tǒng),還是需要添加在項(xiàng)目中。 -
Privacy - Photo Library Additions Usage Description
iOS 11系統(tǒng),在info.plist中添加上這個(gè)property,才能彈出授權(quán)拿到寫權(quán)限來給相冊(cè)添加內(nèi)容。
2.6、AppIcon變化
iOS 11后,Assets.xcassets的AppIcon中增加了App Store圖標(biāo)這一項(xiàng),需要拖入一張1024×1024的Logo圖。