序言
在實際開發(fā)中,我們避免不了需要和 UIWebView 打交道,這就涉及到 JS 與原生 OC 的交互,今天抽空總結(jié)一下 JS 與原生交互使用 UIWebView 攔截 URL 的方式。
JS 調(diào)用原生 OC
我們可以利用 JS 發(fā)起一個假的 URL 請求,然后利用 UIWebView 的代理方法攔截這次請求,然后再做相應(yīng)的處理。
參考網(wǎng)上例子,寫了一個簡單的 HTML 網(wǎng)頁和一個按鈕點擊事件用來和原生 OC 交互,HTML代碼如下:
<html>
<header>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
function showAlert(message){
asyncAlert(message);
}
function asyncAlert(content) {
setTimeout(function(){
alert(content);
},1);
}
function loadURL(url) {
var iFrame;
iFrame = document.createElement("iframe");
iFrame.setAttribute("src", url);
iFrame.setAttribute("style", "display:none;");
iFrame.setAttribute("height", "0px");
iFrame.setAttribute("width", "0px");
iFrame.setAttribute("frameborder", "0");
document.body.appendChild(iFrame);
// 發(fā)起請求后這個 iFrame 就沒用了,所以把它從 dom 上移除掉
iFrame.parentNode.removeChild(iFrame);
iFrame = null;
}
function firstClick() {
loadURL("firstClick://shareClick?title=分享的標(biāo)題&content=分享的內(nèi)容&url=鏈接地址&imagePath=圖片地址");
}
</script>
</header>
<body style="width:100%; height:100%;">
<h2> 這里是第一種方式 </h2>
<br/>
<br/>
<button type="button" onclick="firstClick()">Click Me!</button>
</body>
</html>
雖然 HTML 內(nèi)容比較少,還是有很多學(xué)問的
1.為什么定義一個
loadURL方法,不直接使用window.location.href?
答:因為如果當(dāng)前網(wǎng)頁正在使用window.location.href加載網(wǎng)頁的同時,調(diào)用window.location.href去調(diào)用 OC 原生方法,會導(dǎo)致加載網(wǎng)頁的操作被取消掉。同樣的,如果連續(xù)使用window.location.href執(zhí)行兩次 OC 原生調(diào)用,也有可能導(dǎo)致第一次的操作被取消掉。所以我們使用自定義的loadURL,來避免這個問題。loadURL的實現(xiàn)來自關(guān)于UIWebView和PhoneGap的總結(jié)一文。
2.為什么 loadURL 中的鏈接,使用統(tǒng)一的 scheme?
答:便于在 OC 中做攔截處理,減少在 JS 中調(diào)用一些 OC 沒有實現(xiàn)的方法時,webView 做跳轉(zhuǎn)。我再 OC 攔截 URL 時,根據(jù) scheme即(firstclick)來區(qū)分是調(diào)用原生的方法還是正常的網(wǎng)頁跳轉(zhuǎn)。然后根據(jù)host(即//后面的部分shareClick)來區(qū)分執(zhí)行什么操作。
3.為什么自定義一個asyncAlert方法?
答:因為有的 JS 調(diào)用是需要 OC 返回結(jié)果到 JS 的。
stringByEvaluatingJavaScriptFromString是一個同步方法,會等待 js 方法執(zhí)行完成。而彈出的 alert 也會阻塞界面等待用戶響應(yīng),所以他們可能會造成死鎖。導(dǎo)致 alert 卡死界面。如果回調(diào)的 JS 是一個耗時操作,那么建議將耗時的操作也放入setTimeout的function中。
然后在項目的控制器中實現(xiàn) UIWebView 的代理方法
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL * url = [request URL];
if ([[url scheme] isEqualToString:@"firstclick"]) { // firstClick://shareClick?title=分享的標(biāo)題&content=分享的內(nèi)容&url=鏈接地址&imagePath=圖片地址
NSArray *params = [url.query componentsSeparatedByString:@"&"];
NSMutableDictionary *tempDict = [NSMutableDictionary dictionary];
NSMutableString *strM = [NSMutableString string];
for (NSString *paramStr in params) {
NSArray *dictArray = [paramStr componentsSeparatedByString:@"="];
if (dictArray.count > 1) {
NSString *decodeValue = [dictArray[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
decodeValue = [decodeValue stringByRemovingPercentEncoding];
[tempDict setObject:decodeValue forKey:dictArray[0]];
[strM appendString:decodeValue];
}
}
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"這是OC原生的彈出窗" message:strM delegate:self cancelButtonTitle:@"收到" otherButtonTitles:nil];
[alertView show];
NSLog(@"tempDic:%@",tempDict);
return NO;
}
return YES;
}
1.JS中的firstClick,在攔截到的url scheme全都被轉(zhuǎn)化為小寫。
2.html 中需要設(shè)置編碼,否則中文參數(shù)可能會出現(xiàn)編碼問題。
3.JS用打開一個iFrame的方式替代直接用document.location的方式,以避免多次請求,被替換覆蓋的問題。
OC 調(diào)用 JS
在 OC 原生中
NSString *jsStr = [NSString stringWithFormat:@"showAlert('%@')",@"這里是JS中alert彈出的message"];
[self.webView stringByEvaluatingJavaScriptFromString:jsStr];
注意:該方法會同步返回一個字符串,因此是一個同步方法,可能會阻塞主線程。
在 html 文件中
function showAlert(message) {
alert(message);
}
早期的JS與原生交互的開源庫很多都是用得這種方式來實現(xiàn)的,例如:PhoneGap、WebViewJavascriptBridge。關(guān)于這種方式調(diào)用OC方法,唐巧早期有篇文章有過介紹:
關(guān)于UIWebView和PhoneGap的總結(jié)
效果如下:

更多 JS 與 OC 交互文章請看下面
iOS下 JS 與OC 互相調(diào)用(二) - JavaScriptCore
iOS 下 JS 與 OC 互相調(diào)用(三) - WKWebView 攔截 URL
iOS下JS與OC互相調(diào)用(四)-MessageHandler
iOS下 JS 與 OC 互相調(diào)用(五) - UIWebView+WebViewJavascriptBridge
iOS下 JS 與 OC 互相調(diào)用(六) - WKWebView+WKWebViewJavascriptBridge