編譯器升級xcode9,準備適配一下iphoneX的布局,調試時發現由于一些ios11新特性或者底層變化的原因,xcode9運行出來的項目界面有bug,下面做一下新特性適配和iphoneX布局適配的記錄。
ios11新特性
UIScrollView新特性
UITbaleView自動增加了內邊距.jpeg
- 現象:如上圖所示,圖中中間部分是一個表格,在ios11手機上,表格的頭部多出了一部分空隙。
- 原因:ios11后,UIScrollView增加了新特性,多了一個屬性:contentInsetAdjustmentBehavior,理解為“調整內邊距的行為”,可選四個參數:
-- UIScrollViewContentInsetAdjustmentAutomatic : 自動調整;
-- UIScrollViewContentInsetAdjustmentScrollableAxes : 在滾動方向上調整;
-- UIScrollViewContentInsetAdjustmentNever : 從不調整;
-- UIScrollViewContentInsetAdjustmentAlways : 總是調整。
默認是UIScrollViewContentInsetAdjustmentAutomatic,所以它自動增加了一部分空隙去避免遮擋(自動調整的判斷,包括:是否在導航控制器中、是否在tabbarViewController中等等)。 - 解決方法:
if (@available(iOS 11.0, *))//表示只在ios11以上的版本執行
{
_brandTableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
正常時效果.png
UISearchBar新特性
UISearchBar的bug.jpeg
-
現象:如圖所示,ios11這個searchBar莫名肥了一圈,我代碼沒變過,同樣設的是高度44,在ios10上是下面這個樣子
正常狀態的searchBar.jpeg - 原因:UISearchBar的底層結構變了,簡單來看,以前的UISearchBar是下面這個樣子
searchBar結構示意.png
簡單來看,它由一個大的紅色view+內部輸入框(白色部分)構成,在ios11以前,高度設置控制的是大的紅色view的高度,內部輸入框(白色部分)的高度永遠固定是28。
而ios11后,它沒了大的紅色view,只有內部輸入框(白色部分),給高度的時候,自然就控制的是內部輸入框(白色部分)的高度。
所以同樣都是44的高,ios11上的UISearchBar會肥一圈。 - 解決方法:
如果需要跟以前保持同樣的效果,那么在ios11上把UISearchBar的高度設為28并重新布局
UITableView使用MJRefreshAutoNormalFooter上拉刷新時頁面會跳動
- 現象:正常情況:應該是觸發了上拉刷新,加載數據,加載完后增加若干cell,但頁面仍保持在原位置。而軟件在xcode9編譯環境下的ios11手機上,加載完后頁面會向上跳一截,尤其在cell的高度是由model的實際數據決定,尤其在cell高度較大的時候,很明顯。MJRefresh做了更新,但實測,還是有問題。
- 原因:UITableView這兩個代理方法調用順序變了:cellForRowAtIndexPath和heightForRowAtIndexPath,直接導致了獲取高度算tableview偏移有問題。
- 解決辦法:
實現以下方法:
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 200;
}
盡量給出較接近實際cell高度的數值,跳動問題會消失。
- 2018.02.01更新,不需要實現estimatedHeightForRowAtIndexPath這個代理,設置下面三個參數即可
if (@available(iOS 11.0, *))//表示只在ios11以上的版本執行
{
tableview.estimatedRowHeight = 0;
tableview.estimatedSectionHeaderHeight = 0;
tableview.estimatedSectionFooterHeight = 0;
}
導航欄返回按鈕偏移、自定義按鈕難以點中
返回按鈕偏移bug.jpeg
- 現象:UINavigationController的navBar的返回按鈕,我隱藏了它的文字,結果它比在ios10上,明顯向右向下偏移了一截(不隱藏文字時不會有這種問題)。我自定義了一個左邊按鈕作為返回按鈕,發現非常難點中。
- 原因:底層實現機制改變。
- 解決辦法:
自定義左邊按鈕作為返回按鈕
那么如何解決自定義按鈕難以點中的問題呢?
給navBar添加一個單擊手勢,判斷手勢位置,如果在左邊某個范圍,就調用按鈕的點擊事件。
我是給UIViewController寫了一個分類UIViewController+YYBackBarButton,這樣需要用的頁面里,只需要在viewDidLoad中加下面一句話就行了
[self addCustomBackButtonWithBackButtonColor:UIColorFromRGB(0x979797)];
分類.m文件代碼如下:
#import "UIViewController+YYBackBarButton.h"
#import "UIImage+extend.h"
@implementation UIViewController (YYBackBarButton)
- (void)addCustomBackButtonWithBackButtonColor:(UIColor *)backButtonColor
{
self.navigationController.navigationBar.tintColor = [UIColor clearColor];//返回按鈕顏色
self.navigationController.navigationBar.backItem.hidesBackButton = YES;//隱藏返回按鈕
self.navigationController.interactivePopGestureRecognizer.delegate = (id)self;//開啟手勢右滑返回
//創建返回按鈕
UIButton *backButton = [[UIButton alloc] init];
backButton.frame = CGRectMake(0, 0, 13, 22);
[backButton setImage:[[UIImage imageNamed:@"backButton.png"] imageWithColor:backButtonColor] forState:UIControlStateNormal];//imageWithColor這個方法是UIImage+extend分類里的方法,作用是對圖片的顏色進行自定義渲染(說白了就是改圖片顏色)
backButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
//返回按鈕偏移值,根據自己的需要設置偏移,已達到想要的UI效果
CGFloat insetNumber = 0;
if (@available(iOS 11.0, *))
{
if (kMainScreenHeight == IPHONE_4_SCREEN_HEIGHT) insetNumber = 8;
else if (kMainScreenHeight == IPHONE_5_SCREEN_HEIGHT) insetNumber = 8;
else if (kMainScreenHeight == IPHONE_6_SCREEN_HEIGHT) insetNumber = 8;
else if (kMainScreenHeight == IPHONE_6PLUS_SCREEN_HEIGHT) insetNumber = 12;
else if (kMainScreenHeight == IPHONE_X_SCREEN_HEIGHT) insetNumber = 8;
}
else
{
if (kMainScreenHeight == IPHONE_4_SCREEN_HEIGHT) insetNumber = 8;
else if (kMainScreenHeight == IPHONE_5_SCREEN_HEIGHT) insetNumber = 8;
else if (kMainScreenHeight == IPHONE_6_SCREEN_HEIGHT) insetNumber = 8;
else if (kMainScreenHeight == IPHONE_6PLUS_SCREEN_HEIGHT) insetNumber = 12;
else if (kMainScreenHeight == IPHONE_X_SCREEN_HEIGHT) insetNumber = 8;
}
[backButton setImageEdgeInsets:UIEdgeInsetsMake(0, -insetNumber, 0, insetNumber)];
//添加按鈕事件
[backButton addTarget:self action:@selector(customClickBackButton) forControlEvents:UIControlEventTouchUpInside];
//添加手勢,獲取點擊,避免不容易點中返回按鈕的問題
[self.navigationController.navigationBar addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(clickNavBar:)]];
//把按鈕設置為導航欄左邊按鈕
UIBarButtonItem *leftBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:backButton];
self.navigationItem.leftBarButtonItem = leftBarButtonItem;
}
- (void)customClickBackButton
{
[self.navigationController popViewControllerAnimated:YES];
}
- (void)clickNavBar:(UITapGestureRecognizer *)tap
{
//獲取手勢的點
CGPoint tapPoint = [tap locationInView:self.navigationController.navigationBar];
if (tapPoint.x <= kMainScreenWidth * 0.15)//如果手勢的點在左邊15%,就讓它出發返回按鈕事件
{
[self customClickBackButton];
}
}
@end
其中有個圖片重新渲染顏色的方法,我寫在了UIImage+extend這個分類中,可以改一張圖片的整體顏色,方法實現如下:
- (UIImage *)imageWithColor:(UIColor *)color
{
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, self.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextClipToMask(context, rect, self.CGImage);
[color setFill];
CGContextFillRect(context, rect);
UIImage*newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
這樣處理之后的效果,就跟以前一樣了:
解決后效果.jpeg
鍵盤新特性
IQKeyboardManager鍵盤工具欄bug.jpeg
- 現象:我在項目里使用IQKeyboardManager框架,正常情況下,紅圈處左邊應該有上下跳轉輸入框的按鈕、右邊應該有完成按鈕。
- 原因:ios11鍵盤window底層布局變化。
-
解決辦法:
更新IQKeyboardManager框架(研究了半天原因才發現人家已經更新了做好適配了)。
正常鍵盤工具欄.jpeg
XCode9運行訪問系統相冊崩潰問題
保存圖片崩潰.jpeg
- 現象:如圖保存圖片功能,在XCode9下運行會崩潰
- 原因:info.plist新增了權限配置
-
解決:info.plist新增一條權限:Privacy - Photo Library Additions Usage Description
新增權限.jpeg
iphoneX適配
主要是UI方面的適配,但也發現了一個不明原因的bug。
bug的解決
iphoneX上的bug.gif
- 現象:注意觀察上圖中tabbar的位置。從帶tabbar的控制器push到其它控制器(hidesBottomBarWhenPushed = YES),tabbar在push的時候會突然向上移動一截,但是pop的時候又是正常的,并且同樣是ios11的iphone8上都是正常的。
- 原因:目前不清楚是不是xcode9模擬器的問題,也不知道真機上會不會有。
- 解決:重寫- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated方法,自己控制push時tabbar的frame
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// 重寫super
[super pushViewController:viewController animated:animated];
// 修改tabBra的frame
if (isIPhoneX)//解決push時tabbar瞬間上移的問題
{
CGRect frame = self.tabBarController.tabBar.frame;
frame.origin.y = [UIScreen mainScreen].bounds.size.height - frame.size.height;
self.tabBarController.tabBar.frame = frame;
}
}
即可恢復正常狀態。
UI適配
主要是statusBar(頂部狀態欄)的高度不一樣了、底部增加了一個安全距離。適配思路就是避開這些地方,具體思路下文分開介紹。
頂部狀態欄的問題
頂部遮擋.png
- 現象:如圖所示,頂部的searchView被擋住了一塊。
- 原因:這個頁面的頂部白色部分是一個view,它的高度我寫死為64(普通手機里導航欄+狀態欄整體高度),所以在普通手機里看起就是一個正常的導航欄,而在這里就發生了遮擋,因為普通手機的狀態欄高度是20,而iphoneX是44。
- 解決:所有這種頂部的view,都不應該寫死64的高度,而應該寫成導航欄高度+狀態欄高度。
導航欄高度所有都是44,沒有變化,而狀態欄高度分為20和44,布局時判斷一下是不是iphoneX,數值也隨之變化,最好是寫成宏。
頂部遮擋的解決.png
底部安全距離的問題
iphoneX上tabbar.png
- 現象:如上圖所示,tabbar的高度明顯比普通手機里高得多,增加的區域大概是下圖紅圈的區域:
自動增加的區域.jpeg
這部分的高度,即底部安全距離的高度是34
總所周知,底部出現安全距離,是為了給系統的手勢讓路,用戶在底部黑條附近進行拖動時,可以達到原來home鍵的效果。
然而,通過我在模擬器上的反復實驗,系統手勢的觸發范圍,并沒有34這么高,而是從黑條頂部到屏幕底部這個范圍,這部分的高度,以下稱作系統手勢有效范圍,高度為13。 -
實際問題:上圖是系統處理的tabbar高度。在自己寫的頁面里,就會出現遮擋的問題:
底部遮擋效果.jpeg -
解決:底部兩個按鈕背后有個白色view,它的高度是寫死為54,應該改為54+手勢有效范圍高度,手勢有效范圍高度根據是不是iphoneX賦值為13或0,這樣就完美適配了iphoneX和普通手機,解決效果如下:
底部遮擋問題解決.jpeg
備注
1、干貨分享:對于手機型號的判斷、底部高度、頂部高度等,我都寫成了宏,需要的朋友拿走不謝:
//是否是手機
#define isIPhone (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
//是否是iphoneX
#define isIPhoneX (kMainScreenWidth >= 375.0f && kMainScreenHeight >= 812.0f && isIPhone)
//蘋果X寬高
#define IPHONE_X_SCREEN_WIDTH 375
#define IPHONE_X_SCREEN_HEIGHT 812
//底部安全高度
#define BOTTOM_SAFE_HEIGHT (isIPhoneX ? 34 : 0)
//系統手勢高度
#define SYSTEM_GESTURE_HEIGHT (isIPhoneX ? 13 : 0)
//tabbar高度
#define TABBAR_HEIGHT (49 + BOTTOM_SAFE_HEIGHT)
//狀態欄高度
#define STATUS_HEIGHT (isIPhoneX ? 44 : 20)
//導航欄高
#define NAVBAR_HEIGHT 44