前言(其實就是廢話,可以忽略 = =)
側滑返回手勢是從iOS7開始增加的一個返回操作,經歷了兩年時間估計iPhone用戶大部分都已經忽略了屏幕左上角那個礙眼的按鈕了。之前在網上搜過有關側滑手勢的技術博客,發現大多比較散亂,甚至有很多都是簡單的粘貼復制,并不全面。在這里寫這篇文章的目地,就是希望比較系統把側滑手勢的相關內容做下總結,也希望可以幫助到正在找相關資料的同學。(不知不覺iOS系統版本都已經更新到9了,想想最初還在糾結iOS6和iOS7適配問題,不禁感覺到時間過的飛快。)
正文
側滑手勢在應用的實際場景中應該分為兩種情況
- 使用系統自帶的返回按鈕
- 使用自定義的返回按鈕
那么在項目中我們會對側滑手勢做哪些自定義的操作呢?下面是我自己總結的幾個常用的操作
- 禁用/啟用側滑手勢
- 獲取側滑手勢
好了,下面我們開始進入正題。
先創建一個繼承自 UINavigationController
的子類,然后讓我們來看一下 UINavigationController
的 @property
,可以找到下面這個屬性:
@property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer
這個屬性就是我們的側滑返回手勢,如果你的項目中沒有需求要自定義返回按鈕(雖然我覺得這并不太可能),那么你所需要的操作就非常簡單了,不多說直接上代碼。
self.navigationController.interactivePopGestureRecognizer.enabled = YES; //啟用側滑手勢
self.navigationController.interactivePopGestureRecognizer.enabled = ?NO; //禁用側滑手勢
如果你需要自定義返回按鈕的話,我這有兩套方案供您選擇
1、用自定義的 UIBarButtonItem
替換 navigationController
的 backBarButtonItem
記住是 backBarButtonItem
而不是 leftBarButtonItem
,如果你不小心替換成了 leftBarButtonItem
,那么會直接導致側滑手勢失效。有關 backBarButtonItem
和 leftBarButtonItem
的區別可以參考這篇文章。
- 優點:比較簡單,不需要重新設置側滑手勢的代理自己管理;
- 缺點:只適用于左上角只有一個返回按鈕的需求;
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
//?對按鈕的個性化設定
UIBarButtonItem *barItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
self.navigationItem.leftBarButtonItem = barItem; //側滑手勢失效
self.navigationItem.backBarButtonItem = barItem; //不影響側滑手勢
2、剛才說到 1 的方法只適用于左上角只有一個返回按鈕的情況,那么當我們需要用到多個按鈕的時候該怎么做呢?這個時候就到了方案 2 上場的時候啦!那就是重新設置側滑手勢的代理,并手動管理。上面我們已經創建了一個 UINavigationController
的子類,這里我們姑且稱BSDemoNavigationController
,下面直接看代碼,在代碼中講解。
//首先先讓我們自定義的 UINavigationController 遵守幾個協議
@interface BSDemoNavigationController ()<UINavigationControllerDelegate,UIGestureRecognizerDelegate,BSNavigationBarButtonActionDelegate>
@property(nonatomic,weak) UIViewController* currentShowVC;
@end
@implementation BSDemoNavigationController
-(id)initWithRootViewController:(UIViewController *)rootViewController
{
//覆蓋創建
BSDemoNavigationController* nvc = [super initWithRootViewController:rootViewController];
nav.interactivePopGestureRecognizer.delegate = self;
nvc.delegate = self;
return nvc;
}
#pragma mark - UIGestureRecognizerDelegate
//這個方法在視圖控制器完成push的時候調用
-(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (navigationController.viewControllers.count == 1){
//如果堆棧內的視圖控制器數量為1 說明只有根控制器,將currentShowVC 清空,為了下面的方法禁用側滑手勢
self.currentShowVC = Nil;
}
else{
//將push進來的視圖控制器賦值給currentShowVC
self.currentShowVC = viewController;
}
}
//這個方法是在手勢將要激活前調用:返回YES允許側滑手勢的激活,返回NO不允許側滑手勢的激活
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
//首先在這確定是不是我們需要管理的側滑返回手勢
if (gestureRecognizer == self.interactivePopGestureRecognizer) {
if (self.currentShowVC == self.topViewController) {
//如果 currentShowVC 存在說明堆棧內的控制器數量大于 1 ,允許激活側滑手勢
return YES;
}
//如果 currentShowVC 不存在,禁用側滑手勢。如果在根控制器中不禁用側滑手勢,而且不小心觸發了側滑手勢,會導致存放控制器的堆棧混亂,直接的效果就是你發現你的應用假死了,點哪都沒反應,感興趣是神馬效果的朋友可以自己試試 = =。
return NO;
}
//這里就是非側滑手勢調用的方法啦,統一允許激活
return YES;
}
好了,進行到這里即使我們自定義了 UINavigationController
的返回按鈕,側滑手勢也應該可以正常使用了。但是大家不要高興的太早,這么做完之后如果你的界面中沒有 scrollView 的話確實能過一切順利,反過說就是控制器中有 scrollView 存在,且你的 scrollView 又正好處在觸發側滑手勢的屏幕邊緣的話(比如全屏幕的 UITabelView
,和屏幕等寬的用來展示廣告的 UIScrollView
),這些 scrollView 會隨著你的側滑返回手勢一起滑動,至于效果圖就讓本喵偷下懶吧 = =。
廢話就不多說了,讓我們來做最后的優化吧,畢竟誰不想讓自己的應用盡善盡美呢!
先說下思路吧,我們既然不想同時響應側滑和 scrollView 的滑動事件,那么我要要做的就是讓 scrollView 在側滑手勢判定為失敗后再響應滾動事件。
首先還是在我們自定義的BSDemoNavigationController
中加入如下代碼。
//獲取側滑返回手勢
- (UIScreenEdgePanGestureRecognizer *)screenEdgePanGestureRecognizer
{
UIScreenEdgePanGestureRecognizer *screenEdgePanGestureRecognizer = nil;
if (self.view.gestureRecognizers.count > 0)
{
for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
{
if ([recognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]])
{
screenEdgePanGestureRecognizer = (UIScreenEdgePanGestureRecognizer *)recognizer;
break;
}
}
}
return screenEdgePanGestureRecognizer;
}
然后在需要優化的控制中取得我們 BSDemoNavigationController
的實例對象,如何取得方法很多,self.navigationController
、單例、appDelegate 等都可以,就不一一贅述了,這里我使用的是 在控制器中使用self.navigationController
獲得。
//禁止側滑手勢和tableView同時滑動
BSDemoNavigationController *navController = (BSDemoNavigationController *)self.navigationController;
if ([navController screenEdgePanGestureRecognizer]) {
//指定滑動手勢在側滑返回手勢失效后響應
[self.friendsDemoTableView.panGestureRecognizer requireGestureRecognizerToFail:[navController screenEdgePanGestureRecognizer]];
}
好了到這為止、我們的側滑返回也就大功告成了,文章到這里也就到一段落了。如果你有什么問題歡迎在下面評論提出,如果你有什么更好的解決辦法也希望能夠跟大家一起共享。最后如果你覺得我的文章對你還有點幫助的話就請點下下面的愛心支持我下吧 !
* 更新 *
針對直接使用 backBarButtonItem
前面直接略過可能會導致某些同學造成誤解,在此更新一下并說明簡單說明一下 backBarButtonItem
的用法。
一、理解誤區
誤區一:分不清backBarButtonItem
和 backIndicatorImage
有些同學誤解了 backBarButtonItem
和 backIndicatorImage
,所以在設置的時候就會存在一些困惑。下面我們來看張圖:
看到這張圖片部分同學就可以理解了針對不同的需求應該去修改什么屬性。在這里我也說明一下在哪里設置這兩個屬性。
-
backIndicatorImage
: 是navigationBar
的屬性,一般有需求替換返回圖片的需求時在創建navigationController
時設置一次即可。 -
backBarButtonItem
: 是navigationItem
的屬性,一般有自定義返回按鈕文字的需求時在viewController
中設置
**誤區二:不知道在哪兒設置backBarButtonItem
**
大部分做過 iOS 開發的同學應該都使用過 leftBarButtonItem
和 rightBarButtonItem
(自定義這兩個空間的方法就不在贅述,Google / Baidu 隨便搜下都能出來相關的方法),所以有些同學可能想當然的理解為設置 backBarButtonItem
也是在同樣的地方設置即可。正確的理解如下:
when A -> B : 此時處于 B 控制器 ,導航欄上顯示的 backBarButtonItem
顯示的為 A 控制器的 backBarButtonItem
。如圖
所以當你要 B 控制器導航欄上的返回按鈕時,你需要去修改 A 的
backBarButtonItem
。
二、情景實戰
情景一:自定義 backBarButtonItem
文字
在 A 控制器的 - (void)viewDidLoad
中加入如下方法
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.navigationItem.title = @"Fir Controller";
self.navigationItem.leftItemsSupplementBackButton = YES;
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[button setTitle:@"下一層" forState:UIControlStateNormal];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[button addTarget:self action:@selector(pushAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
UIBarButtonItem *backItem = [[UIBarButtonItem alloc]init];
backItem.title = @"A的BackButtonItem";
self.navigationItem.backBarButtonItem = backItem;
}
運行結果如下:
情景二:自定義返回圖片并隱藏標題
自定義一個 navigationController
并在- (void)viewDidLoad
中加入如下方法
#import "CustomNavigationController.h"
@implementation CustomNavigationController
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController {
if (self = [super initWithRootViewController:rootViewController]) {
UIImage *image = [UIImage imageNamed:@"JWDemo_Back"];
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
self.navigationBar.backIndicatorImage = image;
self.navigationBar.backIndicatorTransitionMaskImage = image;
}
return self;
}
@end
并在 A 控制器的 - (void)viewDidLoad
中加入如下方法
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.navigationItem.title = @"Fir Controller";
self.navigationItem.leftItemsSupplementBackButton = YES;
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[button setTitle:@"下一層" forState:UIControlStateNormal];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[button addTarget:self action:@selector(pushAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
UIBarButtonItem *backItem = [[UIBarButtonItem alloc]init];
backItem.title = @"";
self.navigationItem.backBarButtonItem = backItem;
}
運行結果如下
好了,大家可以自己把玩一下并有自己的理解。如果有錯誤歡迎大家指出。