iOS-拦截导航栏backBarButtonItem事件

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

前言

我们很熟悉也很普通的场景:用户在当前页面填写信息,当点击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 

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)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 originDelegate = objc_getAssociatedObject(self, interactivePopGestureDelegate);
        return [originDelegate gestureRecognizerShouldBegin:gestureRecognizer];
    }
    
    return YES;
}

@end

 

使用场景;

1、控制器中实现TestDelegate协议,也就是点击backBarButtonItem按钮后弹出提示框,让用户决定是否放弃。点击确认按钮后返回上一界面。

#import 
#import "UIViewController+BackButtonEvent.h"

@interface ThirdViewController : UIViewController

@end


#import "ThirdViewController.h"

@interface ThirdViewController () 

@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.;

            }];

     }

}

转载于:https://my.oschina.net/u/1450995/blog/868465

你可能感兴趣的:(iOS-拦截导航栏backBarButtonItem事件)