美文网首页iOS面试题合集
WebView自定义长按图片功能

WebView自定义长按图片功能

作者: lp_lp | 来源:发表于2020-03-01 15:50 被阅读0次

    关于Webview长按图片功能,系统默认自带菜单弹窗,但是某些场景我们需要自定义菜单功能,此时就需要屏蔽系统弹窗,实现自己的弹窗方式。
    因为iOS12之后 UIWebview苹果将要废弃,所以这里以 WKWebview举例说明。

    下面介绍几个用到JS代码:

    屏蔽系统弹窗

    // 当长按时,禁止或显示系统默认菜单
    document.documentElement.style.webkitTouchCallout='none';
    //当长按时,禁止选择内容
    document.documentElement.style.webkitUserSelect='none';
    

    通过坐标获取某个HTML标签元素

    // 通过坐标获取某个位置的元素
    let element = document.elementFromPoint(x,y);
    判断标签元素是否包含某个属性
    // 是否包含 app-press-disabled 属性,如果包含,说明该标签不允许长按事件(我们自定义协定)
    let isCanLong = element.getAttribute("app-press-disabled") == null;
    

    考虑部分场景不需要长按图片的功能,所以可以通过一个协定好的属性来当做长按开关,比如我们用 app-press-disabled 属性来标示禁止某个元素长按手势打开,当 html标签中存在 app-press-disabled 属性,如:<img src='https://image_url' app-press-disabled>,此图片长按无效(具体规则可以自己定义)。

    获取元素标签名称判断是否是某个标签

    // 是否是图片 IMG标签
     let isImgTag = element.tagName.toLowerCase() == "img";
    

    好,以上js代码够我们完成功能,我们将以上代码写入一个js文件:

    // JavaScript 文件 GGWebLongPressImage.js
    // 本段js用于webview长按图片功能设计
    //
    
    // 关闭webview自带的长按事件和弹窗
    document.documentElement.style.webkitTouchCallout='none';
    document.documentElement.style.webkitUserSelect='none';
    
    // 判断图片是否可以触发长按事件脚本
    // 参数:point坐标
    // 返回:String,如果识别图片返回图片地址,否则返回 "not_image"
    function app_isLongPressImageWithPoint(x,y) {
    
        // 通过坐标获取某个位置的元素
        let element = document.elementFromPoint(x,y);
    
        // 是否包含 app-press-disabled 属性,如果包含,说明该标签不允许长按事件(我们自定义协定)
        let isCanLong = element.getAttribute("app-press-disabled") == null;
    
        // 是否是 IMG标签
        let isImgTag = element.tagName.toLowerCase() == "img";
    
        if (isCanLong && isImgTag) {
            return element.src;
        }else {
            return "not_image";
        }
    }
    

    JS脚本代码准备完毕,效果为如果长按坐标位置为图片返回图片URL,如果不为图片或者不满足长按条件,返回 "not_image"。
    为了尽量解耦代码,我们把此功能单独写到WKWebview 的分类中。创建分类:

    WKWebView+LongPress.h
    

    为了保证每个页面此段js生效,我们将js代码插入WKWebview的 userScripts,保证每个页面脚本代码生效。

    /// 加入JS脚本代码
    - (void)addJsCode {
        //获取网页的根域名
        NSString *jsCode = [WKWebView loadJsCodeWithFileName:@"GGWebLongPressImage" withType:@"js"];
        if (jsCode) {
            WKUserScript *cookieInScript = [[WKUserScript alloc] initWithSource:jsCode injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
            [self.configuration.userContentController addUserScript:cookieInScript];
        }
    }
    
    /// 读取本地js文件
    + (NSString *)loadJsCodeWithFileName:(NSString *)name withType:(NSString *)type {
        NSString *filePath = [[NSBundle mainBundle] pathForResource:name ofType:type];
        NSString *jsCode = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
        return jsCode;
    }
    

    添加长按手势:

    /// 添加长按手势
    - (void)addLongPressGesture {
        // 添加长按手势
        UILongPressGestureRecognizer *longGes = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongPressHandler:)];
        longGes.cancelsTouchesInView = NO;
        longGes.delegate = self;
        [self addGestureRecognizer:longGes];
        
        // 植入js脚本
        [self addJsCode];
    }
    

    打开webview多手势开关,前提判断如果手势为长按事件

    /// 多手势开关
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
    {
        //只有当手势为长按手势时反馈,飞长按手势将阻止。
        return [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]];
    }
    

    长按手势selector实现:

    // 长按手势触发调用
    - (void)onLongPressHandler:(UILongPressGestureRecognizer *)longPress {
        
        if (longPress.state == UIGestureRecognizerStateBegan) {
                    
            CGPoint pt = [longPress locationInView:self];
            
            // 执行刚才的js代码,判断是否满足长按需求并且为图片
            NSString *checkLongJs = [NSString stringWithFormat:
                                     @"app_isLongPressImageWithPoint(%f,%f)",
                                     pt.x,pt.y];
            
            
            // 执行拼接好的脚本代码
            [self evaluateJavaScript:checkLongJs completionHandler:^(NSString* callBackString, NSError * _Nullable error) {
                imageUrl = callBackString;
                
                if(![imageUrl isEqualToString:@"not_image"]) { //满足长按图片条件
                  //拿到图片的url,业务代码处理
                }
            }];
        }
    }
    

    以上基本完成长按图片的功能。

    但是我们发现长按图片虽然生效,但是松手的时候,如果图片有其他点击响应,点击事件也被触发,页面会加载。

    我们这里通过延时处理解决的这个问题:
    通过一个属性判断是否为长按事件,如果为长按事件,手指离开屏幕时,禁止页面跳转,完整代码如下:

    WKWebView+LongPress.h

    #import <WebKit/WebKit.h>
    
    // 长按协议
    @protocol WKLongPressDelegate <NSObject>
    - (void)webViewOnLongPressHandlerWithWebView:(WKWebView *)webView withImageUrl:(NSString *)imageUrl;
    @end
    
    @interface WKWebView (LongPress)<UIGestureRecognizerDelegate>
    
    /// 长按手势代理
    @property (nonatomic, weak) id<WKLongPressDelegate> longPressDelegate;
    
    /**
     是否可以跳转,主要用于解决长按事件后,页面再次跳转问题
     *  YES: 不可以,NO可以
     */
    @property (nonatomic, assign)  BOOL isNotPushLink;
    
    
    /**
     添加长按手势
     */
    - (void)addLongPressGesture;
    
    @end
    

    WKWebView+LongPress.m

    #import "WKWebView+LongPress.h"
    #import <objc/runtime.h>
    
    @implementation WKWebView (LongPress)
    
    #pragma mark @property -setter getter
    @dynamic longPressDelegate;
    
    - (void)setLongPressDelegate:(id<WKLongPressDelegate>)longPressDelegate {
        objc_setAssociatedObject(self, @selector(longPressDelegate), longPressDelegate, OBJC_ASSOCIATION_ASSIGN);
    }
    
    - (id)longPressDelegate {
        return objc_getAssociatedObject(self, _cmd);
    }
    
    - (void)setIsNotPushLink:(BOOL)isNotPushLink {
        objc_setAssociatedObject(self, @selector(isNotPushLink), [NSNumber numberWithBool:isNotPushLink], OBJC_ASSOCIATION_ASSIGN);
    }
    
    - (BOOL)isNotPushLink {
        return [objc_getAssociatedObject(self, _cmd) boolValue];
    }
    #pragma mark - End
    
    /**
     添加长按手势
     */
    - (void)addLongPressGesture {
        // 添加长按手势
        UILongPressGestureRecognizer *longGes = [[UILongPressGestureRecognizer alloc] initWithTarget:self
                                                                                              action:@selector(onLongPressHandler:)];
        longGes.cancelsTouchesInView = NO;
        longGes.delegate = self;
        [self addGestureRecognizer:longGes];
        // 植入js脚本
        [self addJsCode];
    }
    
    /**
     加入JS脚本代码
     */
    - (void)addJsCode {
        //获取网页的根域名
        NSString *jsCode = [WKWebView loadJsCodeWithFileName:@"GGWebLongPressImage" withType:@"js"];
        if (jsCode) {
            WKUserScript *cookieInScript = [[WKUserScript alloc] initWithSource:jsCode
                                                                  injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                                               forMainFrameOnly:NO];
            [self.configuration.userContentController addUserScript:cookieInScript];
        }
    }
    
    /// 是否实现了长按代理
    - (BOOL)isOpenLongPressDelegate {
        return (self.longPressDelegate && [self.longPressDelegate respondsToSelector:@selector(webViewOnLongPressHandlerWithWebView:withImageUrl:)]);
    }
    
    
    /// 多手势开关
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
    {
        //只有当手势为长按手势时反馈,飞长按手势将阻止。
        return [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]];
    }
    
    // 长按手势触发调用
    - (void)onLongPressHandler:(UILongPressGestureRecognizer *)longPress {
        
        if (![self isOpenLongPressDelegate]) {
            return;
        }
        
        if (longPress.state == UIGestureRecognizerStateBegan) {
            
            self.isNotPushLink = YES;
            
            __weak typeof(self) weakSelf = self;
            __block NSString *imageUrl = nil;
            
            CGPoint pt = [longPress locationInView:self];
            
            // 判断是否满足长按需求
            NSString *checkLongJs = [NSString stringWithFormat:
                                     @"app_isLongPressImageWithPoint(%f,%f)",
                                     pt.x,pt.y];
            
            
            // 如果图片有点击事件,不触发长按响应
            [self evaluateJavaScript:checkLongJs completionHandler:^(NSString* callBackString, NSError * _Nullable error) {
                imageUrl = callBackString;
                
                if(![imageUrl isEqualToString:@"not_image"]) { //满足长按图片条件
                    if ([weakSelf isOpenLongPressDelegate]) {
                        [weakSelf.longPressDelegate webViewOnLongPressHandlerWithWebView:self withImageUrl:imageUrl];
                    }
                }
            }];
            
        } else if(longPress.state == UIGestureRecognizerStateEnded ||
                  longPress.state == UIGestureRecognizerStateCancelled ||
                  longPress.state == UIGestureRecognizerStateFailed) {
            //延时0.2秒后,取消webview不可跳转链接状态,解决长按跳转问题
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
                self.isNotPushLink = NO;
            });
        }
    }
    
    /**
     读取本地js文件
     
     @param name 文件名称
     @param type 文件类型
     @return 返回js代码
     */
    + (NSString *)loadJsCodeWithFileName:(NSString *)name withType:(NSString *)type {
        
        NSError *error = nil;
        NSString *filePath = [[NSBundle mainBundle] pathForResource:name ofType:type];
        NSString *jsCode = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
        if (error) { NSLog(@"读取js文件失败:error:%@",error); }
        return jsCode;
    }
    
    @end
    

    这个分类完成长按图片的所有功能。

    如何使用?

    #import "WKWebView+LongPress.h"
    
    //实现 WKLongPressDelegate 代理
    
    //初始化 WKWebView
      WKWebView* webView = ....;
    
    // 实现长按web图片代理
      webView.longPressDelegate = self;
    // 添加长按手势
      [webView addLongPressGesture];
    
    //实现WKNavigationDelegate方法
    - (void)webView:(WKWebView*)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    
      // 如果为长按操作,中断页面加载
        if (webView.isNotPushLink) { decisionHandler(WKNavigationActionPolicyCancel); return; }
        
        decisionHandler(WKNavigationActionPolicyAllow);
    }
    
    // 实现长按webview中的图片代理,当满足自定义条件后触发
    - (void)webViewOnLongPressHandlerWithWebView:(WKWebView *)webView withImageUrl:(NSString *)imageUrl {
        
        // 处理长按图片业务代码, 如弹框展示 保存图片、识别二维码 等
    }
    

    方式虽然简单暴力,但是确实能解决长按跳转的燃眉之急。
    UIWebView也是同样逻辑处理即可。

    作者:GarrettGao
    链接:https://www.jianshu.com/p/e189411a2a53

    相关文章

      网友评论

        本文标题:WebView自定义长按图片功能

        本文链接:https://www.haomeiwen.com/subject/rfqckhtx.html