关于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标签
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
// 长按协议
@protocol WKLongPressDelegate
- (void)webViewOnLongPressHandlerWithWebView:(WKWebView *)webView withImageUrl:(NSString *)imageUrl;
@end
@interface WKWebView (LongPress)
/// 长按手势代理
@property (nonatomic, weak) id longPressDelegate;
/**
是否可以跳转,主要用于解决长按事件后,页面再次跳转问题
* YES: 不可以,NO可以
*/
@property (nonatomic, assign) BOOL isNotPushLink;
/**
添加长按手势
*/
- (void)addLongPressGesture;
@end
WKWebView+LongPress.m
#import "WKWebView+LongPress.h"
#import
@implementation WKWebView (LongPress)
#pragma mark @property -setter getter
@dynamic longPressDelegate;
- (void)setLongPressDelegate:(id)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也是同样逻辑处理即可。