前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS小技能:右滑返回

iOS小技能:右滑返回

作者头像
公众号iOS逆向
发布2022-08-22 11:28:10
2.2K0
发布2022-08-22 11:28:10
举报
文章被收录于专栏:iOS逆向与安全

引言

原理:利用系统的返回手势interactivePopGestureRecognizer进行实现

使用场景:返回按钮有点小,不好触发返回时,可借助右滑返回来提升用户体验

I 添加右滑返回手势

1.1 基于全局的UINavigationController基类实现

若项目有全局的UINavigationController基类,给页面添加右滑返回手势

代码语言:javascript
复制
@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;
}

1.2 方法交换(推荐)

往UIViewController 添加forceEnableInteractivePopGestureRecognizer方法将手势返回强制加回来

代码语言:javascript
复制
@implementation UIViewController (ERPPresent13)
+ (void)load {


    
    [self addMethod:self.class method:@selector(forceEnableInteractivePopGestureRecognizer) method:@selector(kunnan_forceEnableInteractivePopGestureRecognizer)];
    
    
}

- (BOOL)kunnan_forceEnableInteractivePopGestureRecognizer {

    
    return YES;

}

II QMUI导致右滑返回没有生效的解决方法

先来看看QMUI如何实现实现右滑返回?

2.1 问题分析

QMUI使用分类UINavigationController (QMUI) 方式进行控制右滑返回,具体核心代码如下

  1. 重写viewDidLoad设置右滑返回手势的代理为自身
代码语言:javascript
复制
        ExtendImplementationOfVoidMethodWithoutArguments([UINavigationController class], @selector(viewDidLoad), ^(UINavigationController *selfObject) {
            selfObject.qmui_interactivePopGestureRecognizerDelegate = selfObject.interactivePopGestureRecognizer.delegate;
            selfObject.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)selfObject;
        });


  1. gestureRecognizerShouldBegin

这个方法是在手势将要激活前调用:返回YES允许右滑手势的激活,返回NO不允许右滑手势的激活

代码语言:javascript
复制
- (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;
}


  1. iOS 13.4 开始会优先询问shouldReceiveEvent方法,只有返回 YES 后才会继续后续的逻辑
代码语言:javascript
复制

- (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 进行判定是否返回。

代码语言:javascript
复制
- (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按钮

代码语言:javascript
复制
- (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 动态添加方法

使用场景:

  1. 在消息发送和消息转发时会用到动态添加方法
  2. 全局控制返回手势

下面的+addMethod方法有三个参数,第一个参数是要添加方法的类,第二个参数是方法的SEL,第三个参数则是提供方法实现的SEL。

使用class_getInstanceMethod()method_getImplementation()获取相应SEL。 下方的IMP其实就是Implementation的方法缩写,获取到相应的方法实现后,然后再调用class_addMethod()方法将IMP与SEL进行绑定即可。

代码语言:javascript
复制

/**
 往类上添加新的方法与其实现
 
 @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方法将手势返回强制加回来

代码语言:javascript
复制
    @implementation UIViewController (ERPPresent13)
+ (void)load {

    [self addMethod:self.class method:@selector(forceEnableInteractivePopGestureRecognizer) method:@selector(kunnan_forceEnableInteractivePopGestureRecognizer)];
    
    
}

- (BOOL)kunnan_forceEnableInteractivePopGestureRecognizer {

    
    return YES;
    


}



III 相关知识点

3.1 WKWebView实现手势左滑返回上一级

代码语言:javascript
复制
        // UI代理
        _webView.UIDelegate = self;
        // 导航代理
        _webView.navigationDelegate = self;
        // 是否允许手势左滑返回上一级, 类似导航控制的左滑返回
        _webView.allowsBackForwardNavigationGestures = YES;
        //可返回的页面列表, 存储已打开过的网页
        WKBackForwardList * backForwardList = [_webView backForwardList];

完整初始化代码

代码语言:javascript
复制
- (WKWebView *)webView{
    if(_webView == nil){
        
        //创建网页配置对象
        WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
        
        // 创建设置对象
        WKPreferences *preference = [[WKPreferences alloc]init];
        //最小字体大小 当将javaScriptEnabled属性设置为NO时,可以看到明显的效果
        preference.minimumFontSize = 0;
        //设置是否支持javaScript 默认是支持的
        preference.javaScriptEnabled = YES;
        // 在iOS上默认为NO,表示是否允许不经过用户交互由javaScript自动打开窗口
        preference.javaScriptCanOpenWindowsAutomatically = YES;
        config.preferences = preference;
        
        // 是使用h5的视频播放器在线播放, 还是使用原生播放器全屏播放
        config.allowsInlineMediaPlayback = YES;
        //设置视频是否需要用户手动播放  设置为NO则会允许自动播放
        config.requiresUserActionForMediaPlayback = YES;
        //设置是否允许画中画技术 在特定设备上有效
        config.allowsPictureInPictureMediaPlayback = YES;
        //设置请求的User-Agent信息中应用程序名称 iOS9后可用
        config.applicationNameForUserAgent = @"ChinaDailyForiPad";
        
        //自定义的WKScriptMessageHandler 是为了解决内存不释放的问题
        WeakWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[WeakWebViewScriptMessageDelegate alloc] initWithDelegate:self];
        //这个类主要用来做native与JavaScript的交互管理
        WKUserContentController * wkUController = [[WKUserContentController alloc] init];
        //注册一个name为jsToOcNoPrams的js方法 设置处理接收JS方法的对象
        [wkUController addScriptMessageHandler:weakScriptMessageDelegate  name:@"jsToOcNoPrams"];
        [wkUController addScriptMessageHandler:weakScriptMessageDelegate  name:@"jsToOcWithPrams"];
        
        config.userContentController = wkUController;
        
        //以下代码适配文本大小
        NSString *jSString = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
        //用于进行JavaScript注入
        WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jSString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
        [config.userContentController addUserScript:wkUScript];
        
        _webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) configuration:config];
        // UI代理
        _webView.UIDelegate = self;
        // 导航代理
        _webView.navigationDelegate = self;
        // 是否允许手势左滑返回上一级, 类似导航控制的左滑返回
        _webView.allowsBackForwardNavigationGestures = YES;
        //可返回的页面列表, 存储已打开过的网页
        WKBackForwardList * backForwardList = [_webView backForwardList];
        
        
        
        NSString *path = [[NSBundle mainBundle] pathForResource:k_localHtml4csdn ofType:nil];
        NSString *htmlString = [[NSString alloc]initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
        [_webView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]];
        
    }
    return _webView;
}

3.2 自定义导航条的rightBarButtonItem

自定义导航条的rightBarButtonItem,采用initWithCustomView:rightBtn设置rightBtn.frame,让文字更大,更容易点击

https://kunnan.blog.csdn.net/article/details/77675855

代码语言:javascript
复制
-(void) setuprightBtn{
    
    
    UIButton *rightBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    rightBtn.frame = CGRectMake(0, 0, 44, 44);
//    [rightBtn setImage:[UIImage imageNamed:@"icon_shoukuan_shaixuan_n"] forState:UIControlStateNormal];
    [rightBtn setTitle:@"编辑" forState:UIControlStateNormal];
    
    [rightBtn setTitleColor:kcellColor forState:UIControlStateNormal];

    [rightBtn addTarget:self action:@selector(gotoEditVC) forControlEvents:UIControlEventTouchUpInside];
    [rightBtn setImageEdgeInsets:UIEdgeInsetsMake(0, 22, 0, 0)];
    
    UIBarButtonItem *rightButtonItem = [[UIBarButtonItem alloc]initWithCustomView:rightBtn];
    
    
    self.navigationItem.rightBarButtonItem = rightButtonItem;

    self.navigationItem.rightBarButtonItem.customView.hidden = YES;



}


see also

iOS运行时API应用:1、实现路由(接口控制app跳任意界面 )2、获取修改对象的成员属性3、动态添加/交换方法的实现4、属性关联

https://kunnan.blog.csdn.net/article/details/112822138

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS逆向 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • I 添加右滑返回手势
    • 1.1 基于全局的UINavigationController基类实现
      • 1.2 方法交换(推荐)
      • II QMUI导致右滑返回没有生效的解决方法
        • 2.1 问题分析
          • 2.2 解决方法
            • 2.3 动态添加方法
            • III 相关知识点
              • 3.1 WKWebView实现手势左滑返回上一级
                • 3.2 自定义导航条的rightBarButtonItem
                • see also
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档