適配iPhoneX & iOS11

1、Screen Size

iPhoneX 的屏幕尺寸爲 375pt × 812pt @3x,像素爲 1125px × 2436px。能夠經過判斷屏幕的高度來判斷設備是不是 iPhoneX,能夠在全局宏定義中添加判斷設備的宏定義(橫豎屏通用):ios

#define IS_IPHONE_X (( fabs((double)[[UIScreen mainScreen] bounds].size.height - (double)812) < DBL_EPSILON ) || (fabs((double)[[UIScreen mainScreen] bounds].size.width - (double)812) < DBL_EPSILON )) 複製代碼

若是在 iPhoneX模擬器運行現有 app,出現上下屏幕沒填充滿的狀況時,說明 app 沒有適合 iPhoneX 尺寸的啓動圖,所以,須要添加一張 1125px × 2436px(@3x)的啓動圖,或者在項目中添加 LaunchScreen.xib,而後在項目的 target 中,設置啓動 Launch Screen File 爲 LaunchScreen.xib。json

2、safe Area

官方指出:安全

When designing for iPhone X, you must ensure that layouts fill the screen and aren't obscured by the device's rounded corners, sensor housing, or the indicator for accessing the Home screen.bash

當咱們在設計 iPhoneX app 的時候,必須確保佈局充滿屏幕,而且佈局不會被設備的圓角、傳感器外殼或者用於訪問主屏幕的指示燈遮擋住。所以,蘋果提出了safe area(安全區)的概念,就是上述可能遮擋界面的區域之外的區域被定義爲安全區。app

豎屏
橫屏

爲了儘量的使佈局和手勢等不被圓角和傳感器遮擋,豎屏狀況下,蘋果官方建議的安全區大小爲上圖(豎屏),指定的狀態欄高度爲 44pt,下方指示燈處的高度爲 34pt;橫屏狀況下爲上圖(橫屏)所示,上下安全邊距分別爲 0pt/21pt,左右安全邊距爲 44pt/34pt,若是使用了UINavigationBarUITabBar,安全區的上邊緣會變成導航欄下邊緣的位置,若是是自定義的navigationBar,而且還繼承於UIView,就須要手動修改狀態欄的高度,在咱們的項目中,狀態欄的高度是用的全局宏定義,所以,修改狀態欄高度的宏定義爲:less

#define STATUS_HEIGHT   (IS_IPHONE_X?44:20)
複製代碼

增長安全區域下面的區域的高度宏定義爲:iphone

#define BOTTOM_SAFEAREA_HEIGHT (IS_IPHONE_X? 34 : 0)
複製代碼

若是你同時也用了自定義的UITabBar那麼就須要修改TABBAR_HEIGHT的宏定義爲:ide

#define TABBAR_HEIGHT   (IS_IPHONE_X? (49 + 34) : 49)
複製代碼

當須要將整個界面最下方的控件上移或者改變中間滾動視圖的高度的時候,使用BOTTOM_SAFEAREA_HEIGHT這個宏,方便後期的統一維護。由於基本上每個界面都須要下方留白,所以在BaseViewController添加屬性:佈局

@property (nonatomic, strong) UIView *areaBelowSafeArea;動畫

而且統一添加到view上:

#warning 背景色待定
if (IS_IPHONE_X) {
   self.areaBelowSafeArea = [[UIView alloc] initWithFrame:CGRectMake(0, SCREEN_HEIGHT - BOTTOM_SAFEAREA_HEIGHT, SCREEN_WIDTH, BOTTOM_SAFEAREA_HEIGHT)];
   // 儘可能使用約束佈局
   self.areaBelowSafeArea.backgroundColor = DefaultTabBarBackgroundColor;
   [self.view addSubview:self.areaBelowSafeArea];
 }
複製代碼

也能夠在特定的viewController中,自定義它的樣式。

self.areaBelowSafeArea.backgroundColor = XXX;
複製代碼

更詳細:Designing for iPhone X

3、UIScrollView及其子類

在 iOS11 中,決定滾動視圖的內容和邊緣距離的屬性改成adjustedContentInset,而不是原來的contentInsets,在 iOS11 以前,UIViewController有一個automaticallyAdjustsScrollViewInsets屬性,而且默認值爲YES,這個屬性的做用爲,當scrollView爲控制器根視圖的最上層子視圖時,若是這個控制器被嵌入到UINavigationControllerUITabBarController中,那麼,它的contentInsets會自動設置爲(64,0,49,0);這個屬性會使滾動視圖中的內容不被導航欄和tabBar遮擋。

iOS11 使用了safeAreaInsets的新屬性,這個屬性的做用就是規定了視圖的安全區的四個邊到屏幕的四個邊的距離,例如在 iPhoneX 上,若是沒使用或者隱藏了UINavigationBar,則safeAreaInsets = (44,0,0,34),若是既使用了UINavigationBar,又使用了UITabBar,則safeAreaInset = (88,0,0,34+49)。也可使用additionalSafeAreaInsets屬性來爲系統默認的safeAreaInsets添加 insets,好比,safeAreaInsets = (44,0,0,34),設置additionalSafeAreaInsets = UIEdgeInsetsMake(-44, 0, 0, 0);,那麼實際上的安全區域到屏幕邊緣的insets爲(0,0,0,34)

adjustedContentInset屬性的值的肯定由 iOS11 API 提供的新的枚舉變量contentInsetAdjustmentBehavior決定。這個屬性的類型定義爲:

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));
複製代碼

四個枚舉值的意義分別爲:

UIScrollViewContentInsetAdjustmentAutomatic

Content is always adjusted vertically when the scroll view is the content view of a view controller that is currently displayed by a navigation or tab bar controller. If the scroll view is horizontally scrollable, the horizontal content offset is also adjusted when there are nonzero safe area insets.

當滾動視圖的父視圖所在的控制器嵌入導航控制器和標籤控制器的時候,滾動視圖的內容總會調整垂直方向上的 insets,若是滾動視圖容許水平方向上可滾動,則當水平方向上的安全區 insets 不爲 0 的時候,也會調整水平方向上的 insets。即:adjustedContentInset = safeAreaInsets + contentInsets,其中contentInsets爲咱們設置的滾動視圖的contentInsets,下同。以下代碼,

self.scroll.contentInset = UIEdgeInsetsMake(100, 0, -34, 10);
if (@available(iOS 11.0, *)) {
    self.scroll.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic;
}else {
    self.automaticallyAdjustsScrollViewInsets = YES;
}
複製代碼

添加導航欄的狀況下,在 iPhoneX 設備上運行打印的 log 爲:

2017-10-20 11:48:56.664048+0800 TestIphoneX[81880:2541000] contentInset:{100, 0, -34, 10}
2017-10-20 11:48:56.664916+0800 TestIphoneX[81880:2541000] adjustedContentInset:{188, 0, 0, 10}
2017-10-20 11:48:56.665220+0800 TestIphoneX[81880:2541000] safeAreaInset:{88, 0, 34, 0}
複製代碼

UIScrollViewContentInsetAdjustmentScrollableAxes

The top and bottom insets include the safe area inset values when the vertical content size is greater than the height of the scroll view itself. The top and bottom insets are also adjusted when the alwaysBounceVertical property is YES. Similarly, the left and right insets include the safe area insets when the horizontal content size is greater than the width of the scroll view.

adjustedContentInset = safeAreaInsets + contentInsets,它的成立依賴於滾動軸,當垂直方向上的contentSize大於滾動視圖的高度時,那麼垂直方向上的 insets 就由safeAreaInsets + contentInsets決定,水平方向上同理。

UIScrollViewContentInsetAdjustmentNever

Do not adjust the scroll view insets.

顧名思義,adjustedContentInset = contentInsets

UIScrollViewContentInsetAdjustmentAlways

Always include the safe area insets in the content adjustment.

顧名思義,adjustedContentInset = safeAreaInsets + contentInsets

因爲咱們的 APP 沒用系統的導航控制器,可是咱們用了系統的標籤控制器,因此在項目中會存在 iOS11 下滾動視圖的位置不對的狀況,那麼就多是由於它的adjustedContentInset = safeAreaInsets + contentInsets形成的,能夠這樣解決:

if (@available(iOS 11.0, *)) {
    self.scroll.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}else {
    self.automaticallyAdjustsScrollViewInsets = NO;
}
複製代碼

4、UITableView sectionHeader 和 sectionFooter高度問題

iOS11 中 UITableView的 sectionHeader 和 sectionFooter 也啓用了 self-sizing,即經過估算的高度乘以個數來肯定tableViewcontenSize的估算值,而後隨着滾動展現 section 和 cell 的過程當中更新它的contenSize,iOS11 以前只有 cell 是採用的這個機制,iOS11中 sectionHeader 和 sectionFooter 也採用了這個機制,而且,若是隻實現了

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
複製代碼

沒實現

- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;
複製代碼

那麼系統就會直接採用估算的高度,而不是heightForHeaderInSection方法中設置的高度,也就是此時的sectionHeight

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section
複製代碼

方法,或者estimatedSectionHeaderHeight設置的估算高度,若是沒有設置估算高度,則系統默認爲UITableViewAutomaticDimension

因此必須同時實現heightForHeaderInSectionviewForHeaderInSection方法,能夠返回[UIView new],可是不能不實現。或者只實現heightForHeaderInSection方法,而且設置estimatedSectionHeaderHeight爲 0 來關閉估算機制。

注:若是在 iOS11 中,使用了 self-sizing cell,而且使用了上拉加載更多,而且使用了高度自適應的方式計算 cell 的高度,那麼上拉加載更多的時候會發現 tableView 會跳動一下或者滾動一段距離,什麼緣由呢,這裏解釋一下多是因爲:假如一個列表有10個 cell,你設置的估算高度是 80,那麼整個列表的估算高度爲10 * 80 = 800,可是實際高度不是 800,假如是 1000,那麼當滾動到最下方的時候,此時的contentOffSet = 1000,而後上拉再加載 10條數據,此時會調用- (void)reloadData;方法,此時,列表的高度仍然會從新使用估算高度計算,80 * 20 = 1600,而contentOffSet = 1000,這個位置已經不是剛纔的第 10條數據了,而是第1000 / 80= 12.5條數據了,所以會形成加載更多的時候數據銜接不上的問題。你可能須要設置estimatedRowHeight = 0來關閉它的估算高度解決這個問題,可是若是你非要開啓它的估算高度來使 cell 中的約束自適應高度的話,能夠經過這種方式計算高度:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static DisplayCell *cell;//‘static’將cell存儲在靜態存儲區,這裏建立的cell僅用來計算高度,所以,內存中只有一份就能夠了,由於此方法會調用屢次,每次都建立的話即會耗費時間也會耗費空間。
    if (!cell) {
        cell = [[[NSBundle mainBundle] loadNibNamed:kCellNibName owner:self options:nil] lastObject];
    }
    cell.displayLab.text = self.data[indexPath.row];//給cell賦值,賦值是爲了經過內容計算高度
    CGFloat height = [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
    return height;
}
複製代碼

另外,若是 cell 中使用了多行 label 的話,注意設置它的換行寬度: self.displayLab.preferredMaxLayoutWidth = XXX;//XXX應該和你約束的label寬度相同

5、Tips

一、每一個界面中的控件的位置

若是項目中用 frame 佈局的控件較多,不少控件的位置依賴於self.view的頂部和底部,因爲狀態欄和底部空間的調整就會形成一部分控件的位置發生變化,修改過程當中應該注意和線上 APP 比對。建議能用約束的就別用 frame,依賴上下控件的位置比依賴屏幕的邊緣和寬高更好維護一些。autolayout 並不影響寫動畫!

二、從新佈局

項目中有些控件的位置會由於響應事件、動畫和數據請求等從新佈局,所以應該特別注意的地方就是從新佈局後控件的位置是否和線上項目一致。另外,還有一些初始化時隱藏的控件,因爲某些條件發生後才展現,也要注意其佈局。

三、全屏顯示

全屏顯示和橫屏模式下的界面,注意橫屏以後下方的感應器在安全區以外。

四、LaunchuImage

像素爲:1125 * 2436 並在LaunchImage中的Contents.json文件中增長 JSON:

{
    "extent" : "full-screen",
    "idiom" : "iphone",
    "subtype" : "2436h",
    "filename" : "圖片名字.png",
    "minimum-system-version" : "11.0",
    "orientation" : "portrait",
    "scale" : "3x"
}
複製代碼

五、定位

在 iOS 11 中必須支持 When In Use 受權模式(NSLocationWhenInUseUsageDescription),在 iOS 11 中,爲了不開發者只提供請求 Always 受權模式這種狀況,加入此限制,若是不提供When In Use 受權模式,那麼 Always 相關受權模式也沒法正常使用。(就是爲了打倒流氓軟件的流氓強制定位)

若是要支持老版本,即 iOS 11 如下系統版本,那麼建議在 info.plist 中配置全部的 Key(即便 NSLocationAlwaysUsageDescription 在 iOS 11及以上版本再也不使用):

NSLocationWhenInUseUsageDescription
NSLocationAlwaysAndWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
複製代碼

NSLocationAlwaysAndWhenInUseUsageDescription爲 iOS 11 中新引入的一個 Key。 參考:WWDC17: What's New in Location Technologies ? (這是一個帶簡體中文字幕的視頻,我並無看!!!)

相關文章
相關標籤/搜索