UINavigationController 全域性右劃導致介面假死問題解決

syik發表於2019-02-21

OS原生是支援邊緣滑動返回的,
但如QQ這種全屏全域性右劃返回顯然使用者體驗更好,
實現起來其實也不麻煩, 但新增這個功能後可能會導致介面卡死問題,
網上的說法有很多而且不全, 其實導致這種問題會有很多種情況, 這裡總結一下:

卡死的原因有三種:

1.在push或pop的過程中, 接收到新的滑動返回手勢.

主要是介面切換快速操作可能出現

解決辦法: 
自定義UINavigationController, 
在pushViewController/setViewControllers/popViewControllerAnimated/popToRootViewControllerAnimated中禁用手勢
在代理方法- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;中啟用手勢
複製程式碼

2.在根控制器右劃返回

在上面的程式碼中增加判斷, 若子控制器數量為1, 禁用手勢
複製程式碼

3.點選navigationItem返回同時觸發了手勢.

一般在自定義了返回按鈕出現, 例如:自定義button的手勢為UIControlEventTouchDown, 全域性右劃返回手勢作用view為self.interactivePopGestureRecognizer.view, 兩者可能會同時觸發

根據原因, 改變手勢作用域
UIView *targetView = [[self.viewControllers lastObject] view];
或改變按鈕事件觸發條件
UIControlEventTouchUpInside
複製程式碼

完整新增全域性右滑返回且無BUG程式碼如下:

@interface LTNavigationController () <UIGestureRecognizerDelegate, UINavigationControllerDelegate>
/**
 是否允許右滑返回
 */
@property (nonatomic, assign, getter=isBackGestureEnable) BOOL backGestureEnable;

@end

@implementation LTNavigationController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.navigationBar.backgroundColor = [UIColor whiteColor];
    //設定全域性右滑返回
    [self setupRightPanReturn];
    
    [self.navigationItem setHidesBackButton:YES];
    
    self.delegate = self;
}



#pragma mark ---每次push之後生成返回按鈕----
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
    if (self.viewControllers.count > 0) {
        viewController.navigationItem.leftBarButtonItem = [UIBarButtonItem BarButtonItemWithImg:@"back_black"  highlightedImg:nil target:self action:@selector(popViewController)];
        viewController.hidesBottomBarWhenPushed = YES;
        viewController.edgesForExtendedLayout = UIRectEdgeNone;
        viewController.automaticallyAdjustsScrollViewInsets = NO;
    }
    
    // push的時候禁用手勢
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.enabled = NO;
        self.backGestureEnable = NO;
    }
    
    [super pushViewController:viewController animated:animated];
}


- (void)setViewControllers:(NSArray<UIViewController *> *)viewControllers animated:(BOOL)animated{
    for (UIViewController *viewController in viewControllers) {
        if (viewController != [viewControllers firstObject]) {
            viewController.navigationItem.leftBarButtonItem = [UIBarButtonItem BarButtonItemWithImg:@"back_black"  highlightedImg:nil target:self action:@selector(popViewController)];
            viewController.hidesBottomBarWhenPushed = YES;
            viewController.edgesForExtendedLayout = UIRectEdgeNone;
            viewController.automaticallyAdjustsScrollViewInsets = NO;
        }
    }
    
    // push的時候禁用手勢
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.enabled = NO;
        self.backGestureEnable = NO;
    }
    
    [super setViewControllers:viewControllers animated:animated];
}


- (void)popViewController{
    [self popViewControllerAnimated:YES];
}


- (UIViewController *)popViewControllerAnimated:(BOOL)animated{
    // pop的時候禁用手勢
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.enabled = NO;
    }
    
    return [super popViewControllerAnimated:animated];
}


- (NSArray<UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated{

    // pop的時候禁用手勢
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.enabled = NO;
    }
    
    return [super popToRootViewControllerAnimated:animated];
}


- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated{

    // push完成後的時候判斷是否在根控制器啟用手勢
    if ([navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        
        if ([navigationController.viewControllers count] == 1) {
            navigationController.interactivePopGestureRecognizer.enabled = NO;
        } else {
            self.backGestureEnable = YES;
            navigationController.interactivePopGestureRecognizer.enabled = YES;
        }
    }
}


#pragma mark ---處理全域性右滑返回---
- (void)setupRightPanReturn{

    // 獲取系統自帶滑動手勢的target物件
    id target = self.interactivePopGestureRecognizer.delegate;
    // 獲取返回方法
    SEL handler = NSSelectorFromString(@"handleNavigationTransition:");
    //  獲取新增系統邊緣觸發手勢的View
        UIView *targetView = self.interactivePopGestureRecognizer.view;
    
    //  建立pan手勢 作用範圍是全屏
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:handler];
    pan.delegate = self;
    [targetView addGestureRecognizer:pan];
    
    // 關閉邊緣觸發手勢 防止和原有邊緣手勢衝突
    [self.interactivePopGestureRecognizer setEnabled:NO];
}

// 什麼時候呼叫:每次觸發手勢之前都會詢問下代理,是否觸發。
// 作用:攔截手勢觸發
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer{
    
    //解決與左滑手勢衝突
    CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
    if (translation.x <= 0 || !self.isBackGestureEnable) {
        return NO;
    }
    return self.childViewControllers.count == 1 ? NO : YES;
}
複製程式碼

喜歡的話給個喜歡或者關注一下 3Q~

相關文章