為了提高用戶體驗,在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添加【邊緣滑動返回】的功能。
這倆不會互相影響,功能互補,詳細原理可以去看看源碼,這兒就不細寫了。