WebView自定義長按圖片功能

關(guān)于Webview長按圖片功能告喊,系統(tǒng)默認(rèn)自帶菜單彈窗闪檬,但是某些場景我們需要自定義菜單功能晨抡,此時就需要屏蔽系統(tǒng)彈窗氛悬,實現(xiàn)自己的彈窗方式则剃。
因為iOS12之后 UIWebview蘋果將要廢棄,所以這里以 WKWebview舉例說明如捅。

下面介紹幾個用到JS代碼:

屏蔽系統(tǒng)彈窗
// 當(dāng)長按時棍现,禁止或顯示系統(tǒng)默認(rèn)菜單
document.documentElement.style.webkitTouchCallout='none';
//當(dāng)長按時,禁止選擇內(nèi)容
document.documentElement.style.webkitUserSelect='none';
通過坐標(biāo)獲取某個HTML標(biāo)簽元素
// 通過坐標(biāo)獲取某個位置的元素
let element = document.elementFromPoint(x,y);
判斷標(biāo)簽元素是否包含某個屬性
// 是否包含 app-press-disabled 屬性镜遣,如果包含己肮,說明該標(biāo)簽不允許長按事件(我們自定義協(xié)定)
let isCanLong = element.getAttribute("app-press-disabled") == null;

考慮部分場景不需要長按圖片的功能,所以可以通過一個協(xié)定好的屬性來當(dāng)做長按開關(guān)悲关,比如我們用 app-press-disabled 屬性來標(biāo)示禁止某個元素長按手勢打開谎僻,當(dāng) html標(biāo)簽中存在 app-press-disabled 屬性,如:<img src='https://image_url' app-press-disabled>寓辱,此圖片長按無效(具體規(guī)則可以自己定義)艘绍。

獲取元素標(biāo)簽名稱判斷是否是某個標(biāo)簽
// 是否是圖片 IMG標(biāo)簽
 let isImgTag = element.tagName.toLowerCase() == "img";

好,以上js代碼夠我們完成功能秫筏,我們將以上代碼寫入一個js文件:

// JavaScript 文件 GGWebLongPressImage.js
// 本段js用于webview長按圖片功能設(shè)計
//

// 關(guān)閉webview自帶的長按事件和彈窗
document.documentElement.style.webkitTouchCallout='none';
document.documentElement.style.webkitUserSelect='none';

// 判斷圖片是否可以觸發(fā)長按事件腳本
// 參數(shù):point坐標(biāo)
// 返回:String,如果識別圖片返回圖片地址诱鞠,否則返回 "not_image"
function app_isLongPressImageWithPoint(x,y) {

    // 通過坐標(biāo)獲取某個位置的元素
    let element = document.elementFromPoint(x,y);

    // 是否包含 app-press-disabled 屬性,如果包含这敬,說明該標(biāo)簽不允許長按事件(我們自定義協(xié)定)
    let isCanLong = element.getAttribute("app-press-disabled") == null;

    // 是否是 IMG標(biāo)簽
    let isImgTag = element.tagName.toLowerCase() == "img";

    if (isCanLong && isImgTag) {
        return element.src;
    }else {
        return "not_image";
    }
}

JS腳本代碼準(zhǔn)備完畢航夺,效果為如果長按坐標(biāo)位置為圖片返回圖片URL,如果不為圖片或者不滿足長按條件鹅颊,返回 "not_image"敷存。
為了盡量解耦代碼,我們把此功能單獨寫到WKWebview 的分類中堪伍。創(chuàng)建分類:

WKWebView+LongPress.h

為了保證每個頁面此段js生效锚烦,我們將js代碼插入WKWebview的 userScripts,保證每個頁面腳本代碼生效帝雇。

/// 加入JS腳本代碼
- (void)addJsCode {
    //獲取網(wǎng)頁的根域名
    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多手勢開關(guān)涮俄,前提判斷如果手勢為長按事件

/// 多手勢開關(guān)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    //只有當(dāng)手勢為長按手勢時反饋,飛長按手勢將阻止尸闸。
    return [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]];
}

長按手勢selector實現(xiàn):

// 長按手勢觸發(fā)調(diào)用
- (void)onLongPressHandler:(UILongPressGestureRecognizer *)longPress {
    
    if (longPress.state == UIGestureRecognizerStateBegan) {
                
        CGPoint pt = [longPress locationInView:self];
        
        // 執(zhí)行剛才的js代碼彻亲,判斷是否滿足長按需求并且為圖片
        NSString *checkLongJs = [NSString stringWithFormat:
                                 @"app_isLongPressImageWithPoint(%f,%f)",
                                 pt.x,pt.y];
        
        
        // 執(zhí)行拼接好的腳本代碼
        [self evaluateJavaScript:checkLongJs completionHandler:^(NSString* callBackString, NSError * _Nullable error) {
            imageUrl = callBackString;
            
            if(![imageUrl isEqualToString:@"not_image"]) { //滿足長按圖片條件
              //拿到圖片的url,業(yè)務(wù)代碼處理
            }
        }];
    }
}

以上基本完成長按圖片的功能吮廉。

但是我們發(fā)現(xiàn)長按圖片雖然生效苞尝,但是松手的時候,如果圖片有其他點擊響應(yīng)宦芦,點擊事件也被觸發(fā)宙址,頁面會加載。

我們這里通過延時處理解決的這個問題:
通過一個屬性判斷是否為長按事件调卑,如果為長按事件抡砂,手指離開屏幕時大咱,禁止頁面跳轉(zhuǎn),完整代碼如下:

WKWebView+LongPress.h

#import <WebKit/WebKit.h>

// 長按協(xié)議
@protocol WKLongPressDelegate <NSObject>
- (void)webViewOnLongPressHandlerWithWebView:(WKWebView *)webView withImageUrl:(NSString *)imageUrl;
@end

@interface WKWebView (LongPress)<UIGestureRecognizerDelegate>

/// 長按手勢代理
@property (nonatomic, weak) id<WKLongPressDelegate> longPressDelegate;

/**
 是否可以跳轉(zhuǎn)注益,主要用于解決長按事件后碴巾,頁面再次跳轉(zhuǎn)問題
 *  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 {
    //獲取網(wǎng)頁的根域名
    NSString *jsCode = [WKWebView loadJsCodeWithFileName:@"GGWebLongPressImage" withType:@"js"];
    if (jsCode) {
        WKUserScript *cookieInScript = [[WKUserScript alloc] initWithSource:jsCode
                                                              injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                                           forMainFrameOnly:NO];
        [self.configuration.userContentController addUserScript:cookieInScript];
    }
}

/// 是否實現(xiàn)了長按代理
- (BOOL)isOpenLongPressDelegate {
    return (self.longPressDelegate && [self.longPressDelegate respondsToSelector:@selector(webViewOnLongPressHandlerWithWebView:withImageUrl:)]);
}


/// 多手勢開關(guān)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    //只有當(dāng)手勢為長按手勢時反饋丑搔,飛長按手勢將阻止厦瓢。
    return [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]];
}

// 長按手勢觸發(fā)調(diào)用
- (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];
        
        
        // 如果圖片有點擊事件,不觸發(fā)長按響應(yīng)
        [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不可跳轉(zhuǎn)鏈接狀態(tài),解決長按跳轉(zhuǎn)問題
        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"

//實現(xiàn) WKLongPressDelegate 代理

//初始化 WKWebView
  WKWebView* webView = ....;

// 實現(xiàn)長按web圖片代理
  webView.longPressDelegate = self;
// 添加長按手勢
  [webView addLongPressGesture];

//實現(xiàn)WKNavigationDelegate方法
- (void)webView:(WKWebView*)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{

  // 如果為長按操作,中斷頁面加載
    if (webView.isNotPushLink) { decisionHandler(WKNavigationActionPolicyCancel); return; }
    
    decisionHandler(WKNavigationActionPolicyAllow);
}

// 實現(xiàn)長按webview中的圖片代理顽冶,當(dāng)滿足自定義條件后觸發(fā)
- (void)webViewOnLongPressHandlerWithWebView:(WKWebView *)webView withImageUrl:(NSString *)imageUrl {
    
    // 處理長按圖片業(yè)務(wù)代碼欺抗, 如彈框展示 保存圖片、識別二維碼 等
}

方式雖然簡單暴力强重,但是確實能解決長按跳轉(zhuǎn)的燃眉之急绞呈。
UIWebView也是同樣邏輯處理即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末间景,一起剝皮案震驚了整個濱河市佃声,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倘要,老刑警劉巖圾亏,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異封拧,居然都是意外死亡志鹃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門泽西,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曹铃,“玉大人,你說我怎么就攤上這事捧杉∩录” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵味抖,是天一觀的道長评甜。 經(jīng)常有香客問我,道長仔涩,這世上最難降的妖魔是什么忍坷? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上承匣,老公的妹妹穿的比我還像新娘。我一直安慰自己锤悄,他們只是感情好韧骗,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著零聚,像睡著了一般袍暴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隶症,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天政模,我揣著相機與錄音,去河邊找鬼蚂会。 笑死淋样,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的胁住。 我是一名探鬼主播趁猴,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彪见!你這毒婦竟也來了儡司?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤余指,失蹤者是張志新(化名)和其女友劉穎捕犬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酵镜,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡碉碉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了笋婿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片誉裆。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖缸濒,靈堂內(nèi)的尸體忽然破棺而出足丢,到底是詐尸還是另有隱情,我是刑警寧澤庇配,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布斩跌,位于F島的核電站,受9級特大地震影響捞慌,放射性物質(zhì)發(fā)生泄漏耀鸦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望袖订。 院中可真熱鬧氮帐,春花似錦、人聲如沸洛姑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽楞艾。三九已至参咙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間硫眯,已是汗流浹背蕴侧。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留两入,地道東北人净宵。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像谆刨,于是被迫代替她去往敵國和親塘娶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345