WebView自定义长按图片功能

关于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也是同样逻辑处理即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335