前言ide
咱们很熟悉也很普通的场景:用户在当前页面填写信息,当点击backBarButtonItem回退按钮准备返回上一界面时,弹出提示框是否放弃这次的输入;若是肯定放弃返回上一界面,不然保留在当前界面。自定义回退事件能够解决这个问题,本篇文章主要是讲经过拦截系统自带的back按钮实现该效果。spa
解决方案;code
一、建立UINavigationController控制器分类,经过runtime交换navigationBar:shouldPopItem:方法(该方法决定是否pop上一界面);orm
二、实现交换后的方法- (BOOL)test_navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;事件
#import "UINavigationController+navigationPopBack.h" #import "UIViewController+BackButtonEvent.h" #import <objc/runtime.h> static void * const interactivePopGestureDelegate = "interactivePopGestureDelegate"; @implementation UINavigationController (navigationPopBack) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self test_swizzleOriginSel:@selector(navigationBar:shouldPopItem:) withSwizzleSel:@selector(test_navigationBar:shouldPopItem:)]; [self test_swizzleOriginSel:@selector(viewDidLoad) withSwizzleSel:@selector(test_viewDidLoad)]; }); } + (void)test_swizzleOriginSel:(SEL)originSel withSwizzleSel:(SEL)swizzleSel { Class curClass = self.class; Method originMethod = class_getInstanceMethod(curClass, originSel); Method swizzleMethod = class_getInstanceMethod(curClass, swizzleSel); BOOL didAddMethod = class_addMethod(curClass, originSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod)); if (didAddMethod) { class_replaceMethod(curClass, swizzleSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod)); } else { method_exchangeImplementations(originMethod, swizzleMethod); } } - (void)test_viewDidLoad { [self test_viewDidLoad]; objc_setAssociatedObject(self, interactivePopGestureDelegate, self.interactivePopGestureRecognizer.delegate, OBJC_ASSOCIATION_ASSIGN); self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self; } - (BOOL)test_navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item { UIViewController *vc = self.topViewController; if (item != vc.navigationItem) { // 当使用popViewControllerAnimated进行pop时,会进入此处,返回YES,即self.topViewController获取的是pop以后的vc。 return YES; } if ([vc conformsToProtocol:@protocol(BackButtonHandler)] && [vc respondsToSelector:@selector(test_navigationShouldPopOnBackButton)]) { if ([vc test_navigationShouldPopOnBackButton]) { return [self test_navigationBar:navigationBar shouldPopItem:item]; } else { for (UIView *subview in [navigationBar subviews]) { if (subview.alpha > 0. && subview.alpha < 1.) { [UIView animateWithDuration:.25 animations:^{ subview.alpha = 1.; }]; } } return NO; } } else { return [self test_navigationBar:navigationBar shouldPopItem:item]; } return NO; } // 拦截侧滑返回事件 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer == self.interactivePopGestureRecognizer) { // 解决根视图侧滑致使push卡死的问题 if (self.viewControllers.count == 1) { return NO; } // 当前视图不是根视图,执行popBack操做 UIViewController *vc = self.topViewController; if([vc respondsToSelector:@selector(test_navigationShouldPopOnBackButton)]) { return [vc test_navigationShouldPopOnBackButton]; } id<UIGestureRecognizerDelegate> originDelegate = objc_getAssociatedObject(self, interactivePopGestureDelegate); return [originDelegate gestureRecognizerShouldBegin:gestureRecognizer]; } return YES; } @end
使用场景;ci
一、控制器中实现TestDelegate协议,也就是点击backBarButtonItem按钮后弹出提示框,让用户决定是否放弃。点击确认按钮后返回上一界面。get
#import <UIKit/UIKit.h> #import "UIViewController+BackButtonEvent.h" @interface ThirdViewController : UIViewController @end #import "ThirdViewController.h" @interface ThirdViewController () <UIAlertViewDelegate> @end @implementation ThirdViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor whiteColor]; self.title = @"胜多负少绝地反击是读后感多个"; UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom]; [backButton setTitle:@"返回" forState:UIControlStateNormal]; [backButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; backButton.frame = CGRectMake(0, 0, 200, 40); backButton.center = self.view.center; [backButton addTarget:self action:@selector(backVC) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:backButton]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)backVC { [self.navigationController popViewControllerAnimated:YES]; } - (BOOL)test_navigationShouldPopOnBackButton { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"舒适提示" message:@"当前数据还没有保存是否放弃本次操做?" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"肯定", nil]; [alertView show]; return NO; } - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 0) { } else { [self.navigationController popViewControllerAnimated:YES]; } } @end
须要注意的地方;animation
一、其实咱们能够发现执行backBarButtonItem的逻辑和经过代码执行popViewControllerAnimated方法的逻辑是不同的,也就是说backBarButtonItem获取到的topViewController是pop前的VC而popViewControllerAnimated获取到的topViewController是pop后的VC,所以item != vc.navigationItem主要判断是否点击backBarButtonItem按钮。it
二、侧滑返回与点击backBarButtonItem按钮返回逻辑一致。io
三、但按钮被点击后箭头会变灰(alpha值被改变),须要经过遍历navigationBar的子view改变其alpha值(IOS7.1以上);
for (UIView *subview in [navigationBar subviews]) {
if (subview.alpha < 1.) {
[UIView animateWithDuration:.25 animations:^{
subview.alpha = 1.;
}];
}
}