前言
今天有個小需求,在點選導航條上的返回按鈕之前要呼叫某個API,並彈出UIAlertView來顯示,根據使用者的選項判斷是否是返回還是繼續留在當前控制器。舉個簡單的例子,當點選導航條上的左上角返回按鈕時,就呼叫我們的API來提示是否知道,點選知道則返回,點選不知道則繼續留在當前控制器。
那麼問題來了,導航自帶的右滑返回手勢在點選系統的返回按鈕時,不會沒有辦法處理,那是自動的,因此就要想辦法改成leftBarButtonItem了,但是使用了leftBarButtonItem就沒有了右滑返回手勢。
魚和熊掌不可兼得?筆者自有辦法!
筆者嘗試寫個demo來驗證有什麼辦法可以解決,嘗試了以下四種:
- 只在當前controller遵守UIGestureRecognizerDelegate並設定代理為self
- 將UIGestureRecognizerDelegate放在公共基類控制器遵守並設定代理為self,然後子類重寫代理方法
- 將UIGestureRecognizerDelegate放在公共導航類HYBNavigationController裡遵守,並設定代理為導航類,然後重寫push/pop相關的所有方法
- 將UIGestureRecognizerDelegate放在公共導航類HYBNavigationController裡遵守,並設定代理為導航類,但是,只遵守-gestureRecognizerShouldBegin:代理方法
方案一(不可行)
方案一:只在當前controller遵守UIGestureRecognizerDelegate並設定代理為self
為什麼不可行呢?當想不測試怎麼知道呢?光想是很難考慮全面的。於是寫了個小demo來測試。
我們在該controller裡這樣寫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (void)viewDidLoad { [super viewDidLoad]; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; [button setTitle:@"返回" forState:UIControlStateNormal]; [button addTarget:self action:@selector(onBack) forControlEvents:UIControlEventTouchUpInside]; [button sizeToFit]; [button setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; UIBarButtonItem *btnItem = [[UIBarButtonItem alloc] initWithCustomView:button]; self.navigationItem.leftBarButtonItem = btnItem; // 關鍵行 self.navigationController.interactivePopGestureRecognizer.delegate = self; } |
一旦設定了代理為self,那麼使用leftBarButtonItem後就可以實現點選回撥,而且右滑手勢還在。
但是,self.navigationController那可是導航控制器物件的的代理被修改當某個控制器物件了,當這個控制器類被釋放後,那麼代理就為nil了,如此就再也沒有右滑返回手勢了。
那麼可能有人會想,在-viewDidAppear:裡設定代理為self,在-viewDidDisappear:時設定代理成原來的代理物件呢?同樣不可以。當A push到B,B push到C,然後從C返回後,代理就不再是最初的導航代理了。
所以,該方案不可行。
方案二(不可行)
方案二:將UIGestureRecognizerDelegate放在公共基類控制器遵守並設定代理為self,然後子類重寫代理方法
筆者嘗試將UIGestureRecognizerDelegate放在HYBBaseViewControlle裡遵守,然後實現代理,預設返回YES,表示支援右滑返回。如果要讓某個控制器不支援右滑返回或者在返回前先執行什麼操作,可以通過重寫此代理方法來實現。
當只在一個控制器裡時,這是可以實現的。但是,當這個控制器被釋放了以後,代理物件就變成了nil了,因此代理是對於導航條物件的,不屬性單個控制器的。
方案三(可行,但複雜)
方案三:將UIGestureRecognizerDelegate放在公共導航類HYBNavigationController裡遵守,並設定代理為導航類,然後重寫push/pop相關的所有方法。
如實現如何下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
// // HYBNavigationController.m // NavRightPanGestureDemo // // Created by huangyibiao on 16/2/22. // Copyright © 2016年 huangyibiao. All rights reserved. // #import "HYBNavigationController.h" #import "HYBBaseViewController.h" @interface HYBNavigationController () @property (nonatomic, assign) BOOL enableRightGesture; @end @implementation HYBNavigationController - (void)viewDidLoad { [super viewDidLoad]; self.enableRightGesture = YES; self.interactivePopGestureRecognizer.delegate = self; } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { return self.enableRightGesture; } - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { if ([viewController isKindOfClass:[HYBBaseViewController class]]) { if ([viewController respondsToSelector:@selector(gestureRecognizerShouldBegin)]) { HYBBaseViewController *vc = (HYBBaseViewController *)viewController; self.enableRightGesture = [vc gestureRecognizerShouldBegin]; } } [super pushViewController:viewController animated:YES]; } - (NSArray *)popToRootViewControllerAnimated:(BOOL)animated { self.enableRightGesture = YES; return [super popToRootViewControllerAnimated:animated]; } - (UIViewController *)popViewControllerAnimated:(BOOL)animated { if (self.viewControllers.count == 1) { self.enableRightGesture = YES; } else { NSUInteger index = self.viewControllers.count - 2; UIViewController *destinationController = [self.viewControllers objectAtIndex:index]; if ([destinationController isKindOfClass:[HYBBaseViewController class]]) { if ([destinationController respondsToSelector:@selector(gestureRecognizerShouldBegin)]) { HYBBaseViewController *vc = (HYBBaseViewController *)destinationController; self.enableRightGesture = [vc gestureRecognizerShouldBegin]; } } } return [super popViewControllerAnimated:animated]; } - (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated { if (self.viewControllers.count == 1) { self.enableRightGesture = YES; } else { UIViewController *destinationController = viewController; if ([destinationController isKindOfClass:[HYBBaseViewController class]]) { if ([destinationController respondsToSelector:@selector(gestureRecognizerShouldBegin)]) { HYBBaseViewController *vc = (HYBBaseViewController *)destinationController; self.enableRightGesture = [vc gestureRecognizerShouldBegin]; } } } return [super popToViewController:viewController animated:animated]; } @end |
這是通過重寫所有的pop/push相關方法,通過判斷是否要求支援右滑來設定。然後,我們要讓某個控制器類在右滑返回或者點選返回之前,先呼叫我們的API判斷,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#import "HYBBController.h" @implementation HYBBController - (void)viewDidLoad { [super viewDidLoad]; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; [button setTitle:@"返回" forState:UIControlStateNormal]; [button addTarget:self action:@selector(onBack) forControlEvents:UIControlEventTouchUpInside]; [button sizeToFit]; [button setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; UIBarButtonItem *btnItem = [[UIBarButtonItem alloc] initWithCustomView:button]; self.navigationItem.leftBarButtonItem = btnItem; } - (BOOL)gestureRecognizerShouldBegin { [self onBack]; return NO; } - (void)onBack { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"標哥的技術部落格" message:@"知道部落格地址是什麼嗎?" delegate:self cancelButtonTitle:@"不知道" otherButtonTitles:@"知道", nil]; [alertView show]; } #pragma mark - UIAlertViewDelegate - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 0) { } else { if ([self.navigationItem.title isEqualToString:@"VC6"]) { NSUInteger index = self.navigationController.viewControllers.count - 3; UIViewController *vc = [self.navigationController.viewControllers objectAtIndex:index]; [self.navigationController popToViewController:vc animated:YES]; } else { [self.navigationController popViewControllerAnimated:YES]; } } } @end |
這種方案確實實現了我們的需求。但是,有沒有更簡單的方案呢?今天可能是眼睛有點困的原因,在研究的時候沒有意識到第四種方案。在我準備寫這篇文章的時候,我再認識地理了一遍邏輯,發現還有非常簡單的一種方案可以實現我的需求。
方案四(可靠,最優)
方案四:將UIGestureRecognizerDelegate放在公共導航類HYBNavigationController裡遵守,並設定代理為導航類,但是,只遵守-gestureRecognizerShouldBegin:代理方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@interface HYBNavigationController () @end @implementation HYBNavigationController - (void)viewDidLoad { [super viewDidLoad]; self.interactivePopGestureRecognizer.delegate = self; } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { BOOL ok = YES; // 預設為支援右滑反回 if ([self.topViewController isKindOfClass:[HYBBaseViewController class]]) { if ([self.topViewController respondsToSelector:@selector(gestureRecognizerShouldBegin)]) { HYBBaseViewController *vc = (HYBBaseViewController *)self.topViewController; ok = [vc gestureRecognizerShouldBegin]; } } return ok; } @end |
使用方法與第三種方案一樣,是不是非常地簡化了?看來是元宵給我的禮物啊,突然想到這樣的辦法。以前一直沒有研究過interactivePopGestureRecognizer屬性,這個屬性是iOS7以後才有的,因此在專案中一直不能直接使用leftBarButtonItem處理,除非那個介面不要右滑返回。
現在,一切都明瞭了,想要使用leftBarButtonItem在公共基類控制器中統一呼叫API來設定就非常簡單了,右滑返回手勢也可以正常使用~
還等什麼,趕緊試試吧!
最後
如果你所使用的專案也有這樣的需求,不防試試吧!筆者提供了demo的,因此可以先下載demo來看看效果哦!經過多次測試,筆者認為這是可行的方案,大家若在使用中出現問題,還請反饋與筆者,我也想了解是什麼情況,當然也要找解決方案,共同進步嘛。
原始碼
請大家到GITHUB下載吧:CoderJackyHuang