(原創)iOS 側滑返回手勢

前言(其實就是廢話,可以忽略 = =)

側滑返回手勢是從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 替換 navigationControllerbackBarButtonItem 記住是 backBarButtonItem 而不是 leftBarButtonItem ,如果你不小心替換成了 leftBarButtonItem ,那么會直接導致側滑手勢失效。有關 backBarButtonItemleftBarButtonItem的區別可以參考這篇文章

  • 優點:比較簡單,不需要重新設置側滑手勢的代理自己管理;
  • 缺點:只適用于左上角只有一個返回按鈕的需求;
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 的用法。

一、理解誤區

誤區一:分不清backBarButtonItembackIndicatorImage
有些同學誤解了 backBarButtonItembackIndicatorImage,所以在設置的時候就會存在一些困惑。下面我們來看張圖:

區分 backBarButtonItem 和 backIndicatorImage

看到這張圖片部分同學就可以理解了針對不同的需求應該去修改什么屬性。在這里我也說明一下在哪里設置這兩個屬性。

  • backIndicatorImage: 是 navigationBar 的屬性,一般有需求替換返回圖片的需求時在創建 navigationController 時設置一次即可。
  • backBarButtonItem : 是 navigationItem 的屬性,一般有自定義返回按鈕文字的需求時在 viewController 中設置

**誤區二:不知道在哪兒設置backBarButtonItem **
大部分做過 iOS 開發的同學應該都使用過 leftBarButtonItemrightBarButtonItem (自定義這兩個空間的方法就不在贅述,Google / Baidu 隨便搜下都能出來相關的方法),所以有些同學可能想當然的理解為設置 backBarButtonItem 也是在同樣的地方設置即可。正確的理解如下:

when A -> B : 此時處于 B 控制器 ,導航欄上顯示的 backBarButtonItem 顯示的為 A 控制器的 backBarButtonItem 。如圖

a.png

所以當你要 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;
}

運行結果如下:


A 改.png

情景二:自定義返回圖片并隱藏標題
自定義一個 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;
}

運行結果如下

自定義圖片.png

好了,大家可以自己把玩一下并有自己的理解。如果有錯誤歡迎大家指出。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容