最近百度了一下原先寫過的文章酣栈,突然發(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瀏覽器的長按事件:
刪除后的頁面為這樣:
紅框之中就是刪掉的圖片中燥。
好了寇甸,不多說了,下面是具體的實現(xiàn)方法:
(一)疗涉、 WKWebView的使用和代理方法啥的網(wǎng)上有很多拿霉,在這里也就不再贅述了。
(二)咱扣、 由于需要有個長按事件绽淘,故我現(xiàn)在我的瀏覽器中長按試了一下,結(jié)果有一個系統(tǒng)自帶的alertController
彈了出來:
具體為什么會彈出這個alert我也不清楚闹伪,我在LLDB中給系統(tǒng)的
[UIAlertController addAction:]
方法下了個斷點:再次執(zhí)行長按方法后沪铭,得到方法調(diào)用的堆棧:
圖中藍(lán)色區(qū)域是這個alert彈出所調(diào)用的方法,我又再次查看wkwebview的代理方法后得知偏瓤,該方法并沒有在代理中實現(xiàn)杀怠,也看不到這個方法的實現(xiàn),經(jīng)過請教別人得知厅克,可以用
method swizzling
去更改這個方法的實現(xiàn)赔退,但這又涉及到了私有API的問題,故放棄证舟。
(三)硕旗、 因為用不到這個系統(tǒng)的alert,所以我先把它隱藏掉:
這里是執(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文件):
然后在代碼中獲取到這些JS代碼而咆,形成一個字符串:
這里是注入方法蛙酪,我是寫了一個寫了一個幫助類來存放這些東西,具體如何視情況而定:
注意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文件中:
通過另一種方法來注入:
這里很容易看懂趴乡,通過一個block當(dāng)注入成功后,會打印"注入成功"蝗拿。
注入成功后晾捏,就可以在刪除廣告的代碼中調(diào)用這個剛剛注入成功的方法了:
removeImgByUrl('%@')
調(diào)用這個JS方法,把img的URL傳進(jìn)去從而刪除掉這個img哀托。
至此通過WKWebView與JS交互來刪除img的方法已經(jīng)實現(xiàn)惦辛,剩下還有點WKWebView點擊和長按手勢的判別需要做,后續(xù)更新吧仓手。
如果有不同想法可以給我留言胖齐,望各位大大不吝言辭玻淑。