前言
我们很熟悉也很普通的场景:用户在当前页面填写信息,当点击backBarButtonItem回退按钮准备返回上一界面时,弹出提示框是否放弃此次的输入;如果确定放弃返回上一界面,否则保留在当前界面。自定义回退事件可以解决这个问题,本篇文章主要是讲通过拦截系统自带的back按钮实现该效果。
解决方案;
1、创建UINavigationController控制器分类,通过runtime交换navigationBar:shouldPopItem:方法(该方法决定是否pop上一界面);
2、实现交换后的方法- (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
使用场景;
1、控制器中实现TestDelegate协议,也就是点击backBarButtonItem按钮后弹出提示框,让用户决定是否放弃。点击确认按钮后返回上一界面。
#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
需要注意的地方;
1、其实我们可以发现执行backBarButtonItem的逻辑和通过代码执行popViewControllerAnimated方法的逻辑是不一样的,也就是说backBarButtonItem获取到的topViewController是pop前的VC而popViewControllerAnimated获取到的topViewController是pop后的VC,因此item != vc.navigationItem主要判断是否点击backBarButtonItem按钮。
2、侧滑返回与点击backBarButtonItem按钮返回逻辑一致。
3、但按钮被点击后箭头会变灰(alpha值被改变),需要通过遍历navigationBar的子view改变其alpha值(IOS7.1以上);
for (UIView *subview in [navigationBar subviews]) {
if (subview.alpha < 1.) {
[UIView animateWithDuration:.25 animations:^{
subview.alpha = 1.;
}];
}
}