序言
在實際開發中,我們避免不了需要和 UIWebView 打交道,這就涉及到 JS 與原生 OC 的交互,今天抽空總結一下 JS 與原生交互使用 UIWebView 攔截 URL 的方式。
JS 調用原生 OC
我們可以利用 JS 發起一個假的 URL 請求,然后利用 UIWebView 的代理方法攔截這次請求,然后再做相應的處理。
參考網上例子,寫了一個簡單的 HTML 網頁和一個按鈕點擊事件用來和原生 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);
// 發起請求后這個 iFrame 就沒用了,所以把它從 dom 上移除掉
iFrame.parentNode.removeChild(iFrame);
iFrame = null;
}
function firstClick() {
loadURL("firstClick://shareClick?title=分享的標題&content=分享的內容&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 內容比較少,還是有很多學問的
1.為什么定義一個
loadURL
方法,不直接使用window.location.href
?
答:因為如果當前網頁正在使用window.location.href
加載網頁的同時,調用window.location.href
去調用 OC 原生方法,會導致加載網頁的操作被取消掉。同樣的,如果連續使用window.location.href
執行兩次 OC 原生調用,也有可能導致第一次的操作被取消掉。所以我們使用自定義的loadURL
,來避免這個問題。loadURL
的實現來自關于UIWebView和PhoneGap的總結一文。
2.為什么 loadURL 中的鏈接,使用統一的 scheme?
答:便于在 OC 中做攔截處理,減少在 JS 中調用一些 OC 沒有實現的方法時,webView 做跳轉。我再 OC 攔截 URL 時,根據 scheme即(firstclick
)來區分是調用原生的方法還是正常的網頁跳轉。然后根據host(即//后面的部分shareClick
)來區分執行什么操作。
3.為什么自定義一個asyncAlert
方法?
答:因為有的 JS 調用是需要 OC 返回結果到 JS 的。
stringByEvaluatingJavaScriptFromString
是一個同步方法,會等待 js 方法執行完成。而彈出的 alert 也會阻塞界面等待用戶響應,所以他們可能會造成死鎖。導致 alert 卡死界面。如果回調的 JS 是一個耗時操作,那么建議將耗時的操作也放入setTimeout
的function
中。
然后在項目的控制器中實現 UIWebView 的代理方法
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL * url = [request URL];
if ([[url scheme] isEqualToString:@"firstclick"]) { // firstClick://shareClick?title=分享的標題&content=分享的內容&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全都被轉化為小寫。
2.html 中需要設置編碼,否則中文參數可能會出現編碼問題。
3.JS用打開一個iFrame的方式替代直接用document.location的方式,以避免多次請求,被替換覆蓋的問題。
OC 調用 JS
在 OC 原生中
NSString *jsStr = [NSString stringWithFormat:@"showAlert('%@')",@"這里是JS中alert彈出的message"];
[self.webView stringByEvaluatingJavaScriptFromString:jsStr];
注意:該方法會同步返回一個字符串,因此是一個同步方法,可能會阻塞主線程。
在 html 文件中
function showAlert(message) {
alert(message);
}
早期的JS與原生交互的開源庫很多都是用得這種方式來實現的,例如:PhoneGap、WebViewJavascriptBridge。關于這種方式調用OC方法,唐巧早期有篇文章有過介紹:
關于UIWebView和PhoneGap的總結
效果如下:
更多 JS 與 OC 交互文章請看下面
iOS下 JS 與OC 互相調用(二) - JavaScriptCore
iOS 下 JS 與 OC 互相調用(三) - WKWebView 攔截 URL
iOS下JS與OC互相調用(四)-MessageHandler
iOS下 JS 與 OC 互相調用(五) - UIWebView+WebViewJavascriptBridge
iOS下 JS 與 OC 互相調用(六) - WKWebView+WKWebViewJavascriptBridge