WKWebView干貨--與JS交互實現(xiàn)可選刪廣告功能。

最近百度了一下原先寫過的文章酣栈,突然發(fā)現(xiàn)是這樣的:
WX20180904-114036@2x.png

也是無語了险胰,下面是正文,雖然寫的不好矿筝,但是是原創(chuàng)起便。

公司項目是一款類似瀏覽器的APP,前兩天需要做一個需求窖维,就是給網(wǎng)頁去除廣告榆综,百度goolge之后,發(fā)現(xiàn)只能通過與JS交互來實現(xiàn)铸史,故在此記錄一下方法鼻疮,由于我不懂JS,所以還需要一段時間來實現(xiàn)琳轿,不過目前已經(jīng)可以實現(xiàn)刪除圖片了判沟。

需求大概是這個樣子:打開某個網(wǎng)頁震贵,長按網(wǎng)頁中某個控件,彈出一個iOS原生的alertController水评,里邊若干個選項中有刪除廣告的選項猩系,點擊后可隱藏網(wǎng)頁中對應(yīng)的標(biāo)簽,類似于UC瀏覽器的長按事件:

D9B84F2B-9AFD-4457-894D-98AD121F2126.png

刪除后的頁面為這樣:

8A19E376-3CDF-42F6-9C90-6BEBF9D0E167.png

紅框之中就是刪掉的圖片中燥。

好了寇甸,不多說了,下面是具體的實現(xiàn)方法:

(一)疗涉、 WKWebView的使用和代理方法啥的網(wǎng)上有很多拿霉,在這里也就不再贅述了。

(二)咱扣、 由于需要有個長按事件绽淘,故我現(xiàn)在我的瀏覽器中長按試了一下,結(jié)果有一個系統(tǒng)自帶的alertController彈了出來:

IMG_0975.PNG

具體為什么會彈出這個alert我也不清楚闹伪,我在LLDB中給系統(tǒng)的[UIAlertController addAction:]方法下了個斷點:
E866B5F2EEFCFCB07A221B5F7C44C4EB.jpg

再次執(zhí)行長按方法后沪铭,得到方法調(diào)用的堆棧:
0ECACD6B0E4F76BB2FABEC33CBE9C5F6.jpg

圖中藍(lán)色區(qū)域是這個alert彈出所調(diào)用的方法,我又再次查看wkwebview的代理方法后得知偏瓤,該方法并沒有在代理中實現(xiàn)杀怠,也看不到這個方法的實現(xiàn),經(jīng)過請教別人得知厅克,可以用method swizzling去更改這個方法的實現(xiàn)赔退,但這又涉及到了私有API的問題,故放棄证舟。

(三)硕旗、 因為用不到這個系統(tǒng)的alert,所以我先把它隱藏掉:


E374EAB8-F8A0-4864-8496-02E27B5D322C.png

這里是執(zhí)行一段JS代碼來屏蔽掉彈出框女责。

(四)漆枚、 屏蔽之后,再次長按網(wǎng)頁鲤竹,果然不會出現(xiàn)這個alert了浪读,那么接下來,自然是要自己加一個長按事件來自定義彈出alertController辛藻,但我在給wkwebview加了長按事件之后,確實可以自定義彈出框互订,但是網(wǎng)頁的長按事件和單擊事件是共存的吱肌,也就是說我長按之后,如果長按的是一個按鈕仰禽,那么他會跳轉(zhuǎn)到下一頁面氮墨,而且網(wǎng)頁中沒有長按狀態(tài)(也就是長按某個控件置灰)纺蛆,故這種方法貌似也行不通。

(五)规揪、 原生方法行不通后桥氏,通過JS來操作網(wǎng)頁貌似是最正確的方法,由于我不會JS猛铅,故去GitHub找到了火狐瀏覽器的源碼字支,期望能從中獲取到什么,果不其然奸忽,從中找到了實現(xiàn)網(wǎng)頁長按獲取控件信息并發(fā)送給iOS的方法代碼:

(function() {
 
 "use strict";
 
 var MAX_RADIUS = 9;
 
 var longPressTimeout = null;
 var touchDownX = 0;
 var touchDownY = 0;
 var highlightDiv = null;
 var touchHandled = false;
 
 function cancel() {
 if (longPressTimeout) {
 clearTimeout(longPressTimeout);
 longPressTimeout = null;
 
 if (highlightDiv) {
 document.body.removeChild(highlightDiv);
 highlightDiv = null;
 }
 }
 }
 
 function createHighlightOverlay(element) {
 // Create a parent element to hold each highlight rect.
 // This allows us to set the opacity for the entire highlight
 // without worrying about overlapping opacities for each child.
 highlightDiv = document.createElement("div");
 highlightDiv.style.pointerEvents = "none";
 highlightDiv.style.top = "0px";
 highlightDiv.style.left = "0px";
 highlightDiv.style.position = "absolute";
 highlightDiv.style.opacity = 0.1;
 highlightDiv.style.zIndex = 99999;
 document.body.appendChild(highlightDiv);
 
 var rects = element.getClientRects();
 for (var i = 0; i != rects.length; i++) {
 var rect = rects[i];
 var rectDiv = document.createElement("div");
 var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
 var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
 var top = rect.top + scrollTop - 2.5;
 var left = rect.left + scrollLeft - 2.5;
 
 // These styles are as close as possible to the default highlight style used
 // by the web view.
 rectDiv.style.top = top + "px";
 rectDiv.style.left = left + "px";
 rectDiv.style.width = rect.width + "px";
 rectDiv.style.height = rect.height + "px";
 rectDiv.style.position = "absolute";
 rectDiv.style.backgroundColor = "#000";
 rectDiv.style.borderRadius = "2px";
 rectDiv.style.padding = "2.5px";
 rectDiv.style.pointerEvents = "none";
 
 highlightDiv.appendChild(rectDiv);
 }
 }
 
 function handleTouchMove(event) {
 if (longPressTimeout) {
 var { screenX, screenY } = event.touches[0];
 // Cancel the context menu if finger has moved beyond the maximum allowed distance.
 if (Math.abs(touchDownX - screenX) > MAX_RADIUS || Math.abs(touchDownY - screenY) > MAX_RADIUS) {
 cancel();
 }
 }
 }
 
 function handleTouchEnd(event) {
 cancel();
 
 removeEventListener("touchend", handleTouchEnd);
 removeEventListener("touchmove", handleTouchMove);
 
 // If we're showing the context menu, prevent the page from handling the click event.
 if (touchHandled) {
 touchHandled = false;
 event.preventDefault();
 }
 }
 
 addEventListener("touchstart", function (event) {
                  // Don't show the context menu for multi-touch events.
                  if (event.touches.length !== 1) {
                  cancel();
                  return;
                  }
                  
                  var data = {};
                  var element = event.target;
                  
                  // Listen for touchend or move events to cancel the context menu timeout.
                  element.addEventListener("touchend", handleTouchEnd);
                  element.addEventListener("touchmove", handleTouchMove);
                  
                  do {
                  if (!data.link && element.localName === "a") {
                  data.link = element.href;
                  
                  // The web view still shows the tap highlight after clicking an element,
                  // so add a delay before showing the long press highlight to avoid
                  // the highlight flashing twice.
                  var linkElement = element;
                  setTimeout(function () {
                             if (longPressTimeout) {
                             createHighlightOverlay(linkElement);
                             }
                             }, 100);
                  }
                  if (!data.image && element.localName === "img") {
                  data.image = element.src;
                  }
                  
                  element = element.parentElement;
                  } while (element);
                  
                  if (data.link || data.image) {
                  var touch = event.touches[0];
                  touchDownX = touch.screenX;
                  touchDownY = touch.screenY;
                  
                  longPressTimeout = setTimeout(function () {
                                                touchHandled = true;
                                                cancel();
                                                webkit.messageHandlers.contextMenuMessageHandler.postMessage(data);
                                                }, 500);
                  
                  webkit.messageHandlers.contextMenuMessageHandler.postMessage({ handled: true });
                  }
                  }, true);
 
 // If the user touches down and moves enough to make the page scroll, cancel the
 // context menu handlers.
 addEventListener("scroll", cancel);
 
 }) ();

從上面代碼可看出堕伪,這個JS方法寫的大概是一個:當(dāng)點擊一個控件,給他一個100毫秒的高亮狀態(tài)栗菜,然后如果是長按的話(按住的時間超過500毫秒)欠雌,通過webkit.messageHandlers.contextMenuMessageHandler.postMessage(data);給iOS發(fā)送信息,其中的data是一個字典疙筹,其中的參數(shù)從代碼中也可看出富俄,參數(shù)為link(按鈕的超鏈接)和image(圖片的URL)。

下面 簡單說下這個JS代碼如何來注入:
先將這個JS代碼存到項目中(創(chuàng)建一個JS文件):

WX20170331-112733@2x.png

然后在代碼中獲取到這些JS代碼而咆,形成一個字符串:


WX20170331-112657@2x.png

這里是注入方法蛙酪,我是寫了一個寫了一個幫助類來存放這些東西,具體如何視情況而定:


CBF7304C-6A58-4C72-B0B2-8350C9E12A63.png

注意WKUserScript類的初始化方法中的后兩個參數(shù):WKUserScriptInjectionTimeAtDocumentEnd這個表示會在網(wǎng)頁加載完之后再執(zhí)行注入翘盖,后邊那個BOOL值表示的是是否只是在主窗口才注入桂塞,所以一定要寫對。

(六)馍驯、 第五步完成之后阁危,就可以在網(wǎng)頁中實現(xiàn)長按,不知道各位還記不記得給iOS發(fā)消息的JS代碼:
webkit.messageHandlers.contextMenuMessageHandler.postMessage(data);汰瘫,<p>WKWebView有專門的協(xié)議方法來接收這一信息:
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
在這個方法的實現(xiàn)中可以拿到JS給iOS的字典data:NSMutableDictionary *data = (NSMutableDictionary *)message.body;狂打,然后通過分析這兩個參數(shù)來創(chuàng)建alertController。

刪除JS控件的代碼(只找到了刪除img的代碼混弥,后續(xù)再更新別的方法):

function removeImgByUrl(url) {
    var x = document.getElementsByTagName("img");
    for (var i = 0; i < x.length; i++){
        if (x[i].src==url){
            x[i].parentNode.remove(x[i]);
        }
    }
}

同樣把它寫入到JS文件中:

B6424109-08AA-4C72-84D5-AD89E2CD6105.png

通過另一種方法來注入:

1DFC24CC-24FE-4F7E-A8B1-421811693C46.png

這里很容易看懂趴乡,通過一個block當(dāng)注入成功后,會打印"注入成功"蝗拿。

注入成功后晾捏,就可以在刪除廣告的代碼中調(diào)用這個剛剛注入成功的方法了:

7942FF51-4D30-4D63-A42D-D59C154E176F.png

removeImgByUrl('%@')調(diào)用這個JS方法,把img的URL傳進(jìn)去從而刪除掉這個img哀托。

至此通過WKWebView與JS交互來刪除img的方法已經(jīng)實現(xiàn)惦辛,剩下還有點WKWebView點擊和長按手勢的判別需要做,后續(xù)更新吧仓手。

如果有不同想法可以給我留言胖齐,望各位大大不吝言辭玻淑。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市呀伙,隨后出現(xiàn)的幾起案子补履,更是在濱河造成了極大的恐慌,老刑警劉巖剿另,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箫锤,死亡現(xiàn)場離奇詭異,居然都是意外死亡驰弄,警方通過查閱死者的電腦和手機(jī)麻汰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來戚篙,“玉大人五鲫,你說我怎么就攤上這事〔砝蓿” “怎么了位喂?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乱灵。 經(jīng)常有香客問我塑崖,道長,這世上最難降的妖魔是什么痛倚? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任规婆,我火速辦了婚禮,結(jié)果婚禮上蝉稳,老公的妹妹穿的比我還像新娘抒蚜。我一直安慰自己,他們只是感情好耘戚,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布嗡髓。 她就那樣靜靜地躺著,像睡著了一般收津。 火紅的嫁衣襯著肌膚如雪饿这。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天撞秋,我揣著相機(jī)與錄音长捧,去河邊找鬼。 笑死部服,一個胖子當(dāng)著我的面吹牛唆姐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播廓八,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼奉芦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了剧蹂?” 一聲冷哼從身側(cè)響起声功,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宠叼,沒想到半個月后先巴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡冒冬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年伸蚯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片简烤。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡剂邮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出横侦,到底是詐尸還是另有隱情挥萌,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布枉侧,位于F島的核電站引瀑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏榨馁。R本人自食惡果不足惜憨栽,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翼虫。 院中可真熱鬧屑柔,春花似錦、人聲如沸蛙讥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽次慢。三九已至旁涤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間迫像,已是汗流浹背劈愚。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留闻妓,地道東北人菌羽。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像由缆,于是被迫代替她去往敵國和親注祖。 傳聞我的和親對象是個殘疾皇子猾蒂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容