為了提高用戶體驗(yàn)锯玛,在controller會加上這個操作咐柜,我自己寫了好多次兼蜈,但是沒有系統(tǒng)的整理過,這會兒又做到這個功能了拙友,索性整理一下为狸。
一、邊緣滑動返回
在遠(yuǎn)古時代遗契,大概是ios7之前辐棒,滑動返回這個事兒是不被官方支持的,因?yàn)槭謾C(jī)屏幕沒那么大牍蜂,IOS7以后漾根,蘋果為了提升用戶體驗(yàn),增加了【邊緣返回】的手勢鲫竞,注意是邊緣立叛,不是全屏,并且在特定條件下贡茅,邊緣返回會失效秘蛇,具體是以下幾種情況:
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ù)組進(jìn)行處理
? ? ? ? //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ù)組進(jìn)行處理
? ? ? ? //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缘屹,來實(shí)現(xiàn):既實(shí)現(xiàn)“自定義返回按鈕(通常自定義leftBarButtonItem或leftBarButtonItems都是為了實(shí)現(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都起作用的問題炊林,默認(rèn)情況下scrollView的手勢會讓系統(tǒng)的手勢失效。so卷要,顯而易見渣聚,我們需要讓兩個手勢同時啟用。
拿捏:
創(chuàng)建UIScrollView的分類
@implementation UIScrollView (PopGesture)
//此方法返回YES時僧叉,手勢事件會一直往下傳遞奕枝,不論當(dāng)前層次是否對該事件進(jìn)行響應(yīng)。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
? ? return YES;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
? ? return YES;
}
@end
二瓶堕、全屏滑動返回
全屏返回這種騷功能隘道,官方從未提供過,可愛的程序員們自己搞出來郎笆,以前的做法(ios7之前)大概就是“手勢+截圖”谭梗,畫風(fēng)是這樣的:
- (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];
}
- 感興趣的同學(xué)可以在 這里?看到完整代碼 -
自從ios7支持【邊緣滑動】返回后宛蚓,【全屏返回】的實(shí)現(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)會打警告??,因?yàn)闆]有申明和實(shí)現(xiàn)代理 ?UIGestureRecognizerDelegate ?钞护,不實(shí)現(xiàn)也木有關(guān)系的盖喷,不過實(shí)現(xiàn)的話,可以再加一些判斷难咕,出于安全和為了去掉警告课梳,我們來加一下距辆。
拿捏:
@interface UIViewController()<UIGestureRecognizerDelegate>
@end
... ...
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer{
? ? //控制器棧里只有一個,不響應(yīng)
? ? if (self.navigationController.viewControllers.count <= 1) {
? ? ? ? return NO;
? ? }
? ? // 當(dāng)控制器正在返回的時候暮刃,不響應(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;
}
還有一處細(xì)節(jié)跨算,每個UIViewController都會默認(rèn)添加 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 主要是實(shí)現(xiàn)【邊緣滑動返回】功能盅抚,不過這個是次要漠魏,主要是,它提供了給UIScrollView添加【邊緣滑動返回】的功能妄均。
這倆不會互相影響柱锹,功能互補(bǔ),詳細(xì)原理可以去看看源碼丰包,這兒就不細(xì)寫了奕纫。