iOS-攔截導航欄backBarButtonItem事件

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

            }];

     }

}

相關文章
相關標籤/搜索