引言
原理:利用系统的返回手势interactivePopGestureRecognizer进行实现
使用场景:返回按钮有点小,不好触发返回时,可借助右滑返回来提升用户体验
I 、添加右滑返回手势
若项目有全局的UINavigationController基类,给页面添加右滑返回手势
@implementation NavigationController - (void)viewDidLoad { [super viewDidLoad]; //设置右滑返回手势的代理为自身 __weak typeof(self) weakself = self; if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.interactivePopGestureRecognizer.delegate = (id)weakself; } } #pragma mark - UIGestureRecognizerDelegate //这个方法是在手势将要激活前调用:返回YES允许右滑手势的激活,返回NO不允许右滑手势的激活 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer == self.interactivePopGestureRecognizer) { //屏蔽调用rootViewController的滑动返回手势,避免右滑返回手势引起死机问题 if (self.viewControllers.count < 2 || self.visibleViewController == [self.viewControllers objectAtIndex:0]) { return NO; } } //这里就是非右滑手势调用的方法啦,统一允许激活 return YES; }
II、QMUI导致右滑返回没有生效的解决方法
先来看看QMUI如何实现实现右滑返回?
2.1 UINavigationController (QMUI)
进行控制右滑返回
QMUI使用分类UINavigationController (QMUI)
方式进行控制右滑返回,具体核心代码如下
- 重写viewDidLoad设置右滑返回手势的代理为自身
ExtendImplementationOfVoidMethodWithoutArguments([UINavigationController class], @selector(viewDidLoad), ^(UINavigationController *selfObject) { selfObject.qmui_interactivePopGestureRecognizerDelegate = selfObject.interactivePopGestureRecognizer.delegate; selfObject.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)selfObject; });
- gestureRecognizerShouldBegin
这个方法是在手势将要激活前调用:返回YES允许右滑手势的激活,返回NO不允许右滑手势的激活
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer == self.interactivePopGestureRecognizer) { BOOL canPopViewController = [self canPopViewController:self.topViewController byPopGesture:YES]; if (canPopViewController) { if ([self.qmui_interactivePopGestureRecognizerDelegate respondsToSelector:_cmd]) { return [self.qmui_interactivePopGestureRecognizerDelegate gestureRecognizerShouldBegin:gestureRecognizer]; } else { return NO; } } else { return NO; } } return YES; }
- iOS 13.4 开始会优先询问
shouldReceiveEvent
方法,只有返回 YES 后才会继续后续的逻辑
- (BOOL)_gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveEvent:(UIEvent *)event { if (gestureRecognizer == self.interactivePopGestureRecognizer) { NSObject <UIGestureRecognizerDelegate> *originGestureDelegate = self.qmui_interactivePopGestureRecognizerDelegate; if ([originGestureDelegate respondsToSelector:_cmd]) { BOOL originalValue = YES; [originGestureDelegate qmui_performSelector:_cmd withPrimitiveReturnValue:&originalValue arguments:&gestureRecognizer, &event, nil]; if (!originalValue && [self shouldForceEnableInteractivePopGestureRecognizer]) { return YES; } return originalValue; } } return YES; }
其中在第三步中shouldForceEnableInteractivePopGestureRecognizer
调用了UINavigationControllerBackButtonHandlerProtocol协议的forceEnableInteractivePopGestureRecognizer
进行判定是否返回。
- (BOOL)shouldForceEnableInteractivePopGestureRecognizer { UIViewController *viewController = [self topViewController]; return self.viewControllers.count > 1 && self.interactivePopGestureRecognizer.enabled && [viewController respondsToSelector:@selector(forceEnableInteractivePopGestureRecognizer)] && [viewController forceEnableInteractivePopGestureRecognizer]; }
当自定义了
leftBarButtonItem
按钮之后,系统的手势返回就失效了。可以通过
forceEnableInteractivePopGestureRecognizer
来决定要不要把那个手势返回强制加回来。当 interactivePopGestureRecognizer.enabled = NO 或者当前UINavigationController
堆栈的viewControllers小于2的时候此方法无效。
自定义了leftBarButtonItem
按钮
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{ viewController.hidesBottomBarWhenPushed = self.viewControllers.count == 1; if (self.viewControllers.count>0) { [self setNavigationBarHidden:NO animated:NO]; // viewController.hidesBottomBarWhenPushed =YES; //设置左边按钮 UIBarButtonItem *backItem =nil; if ([viewController respondsToSelector:@selector(KNbackAction)]) { backItem =[[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:QCTNAVicon_left] style:0 target:viewController action:@selector(KNbackAction)]; }else{ backItem =[[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:QCTNAVicon_left] style:0 target:self action:@selector(backAction)]; } viewController.navigationItem.leftBarButtonItems = @[backItem]; } [super pushViewController:viewController animated:animated]; }
2.2 解决方法
所以当你自定义导航栏(自定义了leftBarButtonItem
按钮)没采用系统的默认的实现,发生当前不可以手势返回,可先检查为什么当前状态,系统不允许你的手势返回,例如是否隐藏了 navigationBar,或者隐藏了系统的返回按钮?
比如push的时候,自定义了leftBarButtonItem
按钮了,你可以采用分类方式往UIViewController 添加forceEnableInteractivePopGestureRecognizer方法将手势返回强制加回来
2.3 动态添加方法
使用场景:
- 在消息发送和消息转发时会用到动态添加方法
- 全局控制返回手势
下面的+addMethod
方法有三个参数,第一个参数是要添加方法的类,第二个参数是方法的SEL,第三个参数则是提供方法实现的SEL。
使用
class_getInstanceMethod()
和method_getImplementation()
获取相应SEL。下方的IMP其实就是Implementation的方法缩写,获取到相应的方法实现后,然后再调用class_addMethod()方法将IMP与SEL进行绑定即可。
/** 往类上添加新的方法与其实现 @param class 相应的类 @param methodSel 添加的方法 @param methodSelImpl 包含方法实现的SEL */ + (void)addMethod:(Class)class method:(SEL)methodSel method:(SEL)methodSelImpl { Method method = class_getInstanceMethod(class, methodSelImpl); IMP methodIMP = method_getImplementation(method); const char *types = method_getTypeEncoding(method); class_addMethod(class, methodSel, methodIMP, types); }
往UIViewController 添加forceEnableInteractivePopGestureRecognizer方法将手势返回强制加回来
@implementation UIViewController (ERPPresent13) + (void)load { [self addMethod:self.class method:@selector(forceEnableInteractivePopGestureRecognizer) method:@selector(kunnan_forceEnableInteractivePopGestureRecognizer)]; } - (BOOL)kunnan_forceEnableInteractivePopGestureRecognizer { return YES; }
see also
iOS运行时API应用:1、实现路由(接口控制app跳任意界面 )2、获取修改对象的成员属性3、动态添加/交换方法的实现4、属性关联