IOS手勢滑動返回總結(jié)(邊緣+全屏)

為了提高用戶體驗,在controller會加上這個操作,我自己寫了好多次,但是沒有系統(tǒng)的整理過,這會兒又做到這個功能了,索性整理一下。

一、邊緣滑動返回

在遠古時代,大概是ios7之前,滑動返回這個事兒是不被官方支持的,因為手機屏幕沒那么大,IOS7以后,蘋果為了提升用戶體驗,增加了【邊緣返回】的手勢,注意是邊緣,不是全屏,并且在特定條件下,邊緣返回會失效,具體是以下幾種情況:

1. 自定義了navigationItem的leftBarButtonItem或leftBarButtonItems

2. self.navigationItem.hidesBackButton = YES

3. self.navigationItem.leftItemsSupplementBackButton = NO


為了解決以上問題,有兩個方案,拿捏:


方案一:

在UINavigationController基類添加以下:

- (void)viewDidLoad

{

? ? [super viewDidLoad];

? ? //設(shè)置右滑返回手勢的代理為自身

? ? __weak typeof(self) weakself = self;

? ? if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {

? ? ? ? self.interactivePopGestureRecognizer.delegate = (id)weakself;

? ? }

}

#pragma mark - UIGestureRecognizerDelegate

//這個方法是在手勢將要激活前調(diào)用:返回YES允許右滑手勢的激活,返回NO不允許右滑手勢的激活

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

{

? ? if (gestureRecognizer == self.interactivePopGestureRecognizer) {

? ? ? ? //屏蔽調(diào)用rootViewController的滑動返回手勢,避免右滑返回手勢引起crash

? ? ? ? if (self.viewControllers.count < 2 ||

self.visibleViewController == [self.viewControllers objectAtIndex:0]) {

? ? ? ? ? ? return NO;

? ? ? ? }

? ? }

? ? //這里就是非右滑手勢調(diào)用的方法啦,統(tǒng)一允許激活

? ? return YES;

}


那么,在特定場景下,我們不希望用戶輕易返回,比如在直播間內(nèi)、在掃碼界面等,拿捏:


創(chuàng)建一個UIViewController 的分類:

+ (void)popGestureClose:(UIViewController *)VC

{

? ? // 禁用側(cè)滑返回手勢

? ? if ([VC.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {

? ? ? ? //這里對添加到右滑視圖上的所有手勢禁用

? ? ? ? for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) {

? ? ? ? ? ? popGesture.enabled = NO;

? ? ? ? }

? ? ? ? //若開啟全屏右滑,不能再使用下面方法,請對數(shù)組進行處理

? ? ? ? //VC.navigationController.interactivePopGestureRecognizer.enabled = NO;

? ? }

}

+ (void)popGestureOpen:(UIViewController *)VC

{

? ? // 啟用側(cè)滑返回手勢

? ? if ([VC.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {

? ? //這里對添加到右滑視圖上的所有手勢啟用

? ? ? ? for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) {

? ? ? ? ? ? popGesture.enabled = YES;

? ? ? ? }

? ? ? ? //若開啟全屏右滑,不能再使用下面方法,請對數(shù)組進行處理

? ? ? ? //VC.navigationController.interactivePopGestureRecognizer.enabled = YES;

? ? }

}


使用:

- (void)viewDidAppear:(BOOL)animated

{

? ? [super viewDidAppear:animated];

? ? [UIViewController popGestureClose:self]; //關(guān)閉邊緣返回

}

- (void)viewWillDisappear:(BOOL)animated

{

? ? [super viewWillDisappear:animated];

? ? [UIViewController popGestureOpen:self]; //啟動邊緣返回

}


方案二:

每個UIViewController都有一個backBarButtonItem,這是個特殊屬性,只響應(yīng)頁面的返回和銷毀,表現(xiàn)為:只能自定義image和title,不能重寫target 或 action。(注意:UINavigationController的左側(cè)是不支持右滑返回手勢的)我們通過自定義backBarButtonItem,來實現(xiàn):既實現(xiàn)“自定義返回按鈕(通常自定義leftBarButtonItem或leftBarButtonItems都是為了實現(xiàn)自定義返回按鈕)”又保留滑動返回。

拿捏:

在UIViewController基類:

- (void)viewDidLoad{


? ? [super viewDidLoad];

UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];

? ? //自定義返回按鈕的視圖

? ? [self.navigationController.navigationBar setBackIndicatorImage:[UIImage imageNamed:@"navi_back_icon"]];

? ? [self.navigationController.navigationBar setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"navi_back_icon"]];

? ? //設(shè)置tintColor 改變自定圖片顏色

? ? self.navigationController.navigationBar.tintColor = [UIColor whiteColor];

? ? //設(shè)置自定義的返回按鈕

? ? self.navigationItem.backBarButtonItem = backItem;

}

那么在這種方案下,在特定場景我們不希望用戶輕易返回,如何做?

拿捏:

自定義`leftBarButtonItem`或`leftBarButtonItems`,并設(shè)置`leftItemsSupplementBackButton = YES`。

- (void)viewDidLoad{


? ? [super viewDidLoad];

? ? //自定義返回按鈕

? ? UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];

? ? [studySearch setImage:[UIImage imageNamed:@"back_icon"] forState:UIControlStateNormal];

? ? [studySearch sizeToFit];

? ? [studySearch addTarget:self action:@selector(backAction) forControlEvents:UIControlEventTouchUpInside];

? ? UIBarButtonItem *studySearchItem = [[UIBarButtonItem alloc] initWithCustomView:studySearch];

? ? self.navigationItem.leftBarButtonItems = @[studySearchItem];

? ? //是否支持顯示左滑返回按鈕,

? ? //NO不顯示:leftBarButtonItems覆蓋backBarButtonItem,

? ? //YES顯示:backBarButtonItem 顯示在leftBarButtonItems左側(cè)

? ? //leftItemsSupplementBackButton必須在自定義leftBarButtonItem或leftBarButtonItems后才有效

? ? self.navigationItem.leftItemsSupplementBackButton = YES;

}

以上兩個方案已經(jīng)可以滿足大部分開發(fā)需求,但還有一種情況,在UIScrollView(UICollectionView)下,返回手勢會失靈。

我們先來看看啥原理:

UIScrollView(包括其子類UITextView、UITableView、UICollectionView等)的panGestureRecognizer先接收到手勢事件,處理后不再往下傳遞。即是否讓兩個panGestureRecognizer都起作用的問題,默認情況下scrollView的手勢會讓系統(tǒng)的手勢失效。so,顯而易見,我們需要讓兩個手勢同時啟用。

拿捏:

創(chuàng)建UIScrollView的分類

@implementation UIScrollView (PopGesture)

//此方法返回YES時,手勢事件會一直往下傳遞,不論當前層次是否對該事件進行響應(yīng)。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{

? ? return YES;

}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{

? ? return YES;

}

@end



二、全屏滑動返回

全屏返回這種騷功能,官方從未提供過,可愛的程序員們自己搞出來,以前的做法(ios7之前)大概就是“手勢+截圖”,畫風是這樣的:

- (void)viewDidLoad{

? ? [super viewDidLoad];

? ? UIImageView *shadowImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"leftside_shadow_bg"]];

? ? shadowImageView.frame = CGRectMake(-10, 0, 10, self.view.frame.size.height);

? ? [self.view addSubview:shadowImageView];


? ? UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(paningGestureReceive:)];

? ? [recognizer setDelegate:self];

? ? [recognizer delaysTouchesBegan];

? ? [self.view addGestureRecognizer:recognizer];

}


- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{

? ? if (self.viewControllers.count > 0) {

? ? ? ? [self.screenShotsList addObject:[self capture]]; //截圖,并放入數(shù)組

? ? }

? ? [super pushViewController:viewController animated:animated];

}

- (nullable NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated {

? ? [self.screenShotsList removeAllObjects]; //清空截圖

? ? return [super popToRootViewControllerAnimated:animated];

}

- 感興趣的同學可以在 這里?看到完整代碼 -


自從ios7支持【邊緣滑動】返回后,【全屏返回】的實現(xiàn)又多了一種思路,江湖人稱:移花接木。同樣有兩個方案。


拿捏:


方案一:

在UINavigationController基類中:


- (void)viewDidLoad{

? ? [super viewDidLoad];

? ? // 獲取系統(tǒng)自帶滑動手勢的target對象

? ? id target = self.interactivePopGestureRecognizer.delegate;

? ? // 創(chuàng)建全屏滑動手勢,調(diào)用系統(tǒng)自帶滑動手勢的target的action方法

? ? SEL handler = NSSelectorFromString(@"handleNavigationTransition:");

? ? UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:handler];

//設(shè)置手勢代理,攔截手勢觸發(fā)

pan.delegate = self;

//添加全屏滑動手勢

[self.interactivePopGestureRecognizer.view addGestureRecognizer:pan];

// 禁止使用系統(tǒng)自帶的邊緣滑動手勢

self.interactivePopGestureRecognizer.enabled = NO;

? ? //設(shè)置右滑返回手勢的代理為自身

? ? __weak typeof(self) weakself = self;

? ? if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {

? ? ? ? self.interactivePopGestureRecognizer.delegate = (id)weakself;

? ? }

}

注意,這里的 ?pan.delegate = self; ?可能系統(tǒng)會打警告??,因為沒有申明和實現(xiàn)代理 ?UIGestureRecognizerDelegate ?,不實現(xiàn)也木有關(guān)系的,不過實現(xiàn)的話,可以再加一些判斷,出于安全和為了去掉警告,我們來加一下。

拿捏:

@interface UIViewController()<UIGestureRecognizerDelegate>

@end

... ...

- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer{

? ? //控制器棧里只有一個,不響應(yīng)

? ? if (self.navigationController.viewControllers.count <= 1) {

? ? ? ? return NO;

? ? }

? ? // 當控制器正在返回的時候,不響應(yīng)

? ? if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {

? ? ? ? return NO;

? ? }

? ? //只能響應(yīng) 從左到右的滑動

? ? CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];

? ? if (translation.x <= 0) {

? ? ? ? return NO;

? ? }

? ? return YES;

}

還有一處細節(jié),每個UIViewController都會默認添加 navigationController.interactivePopGestureRecognizer手勢,而我們再基類又給加了一次,這不是變成兩個interactivePopGestureRecognizer了嗎,既然如此,我們禁用掉一個!

拿捏:

在UIViewController基類中:

- (void)viewDidLoad{

? ? [super viewDidLoad];

? if(self.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers.count == 2 ){

? ? ? ? for (UIGestureRecognizer *popGesture in self.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) {

? ? ? ? ? ? popGesture.enabled = NO;

? ? ? ? ? ? break;

? ? ? ? }

? ? }

}


方案二

此方案最方便快捷。

pod 'FDFullscreenPopGesture'

pod 'TZScrollViewPopGesture'

- FDFullscreenPopGesture 為每個UIViewController添加【全屏滑動返回】,但遇到UIScrollView就無效了

- TZScrollViewPopGesture 主要是實現(xiàn)【邊緣滑動返回】功能,不過這個是次要,主要是,它提供了給UIScrollView添加【邊緣滑動返回】的功能。

這倆不會互相影響,功能互補,詳細原理可以去看看源碼,這兒就不細寫了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容