前言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.;
}];
}
}