iOS發(fā)展至今, 各種hybird方案層出不窮. 最經(jīng)典的hybird方案莫過(guò)于在native框架中, 使用UIWebView來(lái)展示HTML頁(yè)面. 那么我們需要去考慮怎么與web前端進(jìn)行交互. 最近項(xiàng)目中有與web前端進(jìn)行交互, 遇到了一些坑點(diǎn), 故而在這里整理下UIWebView和iOS進(jìn)行交互的方案.
一、協(xié)議攔截
(1)、JS調(diào)用OC:
邏輯: 點(diǎn)擊JS中的button, 傳遞一個(gè)參數(shù)給OC, OC將傳遞來(lái)的參數(shù)打印出來(lái)
-
實(shí)現(xiàn):
JS調(diào)用OC JS中代碼:
<div style="height: 200px">
<button onclick = "clickAction0(1111111)" style = "font-size: 40px; width: 400px; height: 100px" >
button
</button>
</div>
function clickAction0(clickId) {
var token = "js_tokenString";
loginSucceed(token);
}
function loginSucceed(token) {
var action = "loginSucceed";
jsToOc(action, token);
}
function jsToOc(action, params) {
var url = "jsToOc://" + action + "?" + params;
loadURL(url);
}
function loadURL(url) {
window.location.href = url;
}
- OC代碼:
//!< uwebView每次加載請(qǐng)求前都會(huì)調(diào)用這個(gè)方法
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
//!< 通過(guò)判斷URL.scheme來(lái)判斷是否是JS中定義好的方法
if ([request.URL.scheme caseInsensitiveCompare:@"jsToOc"] == NSOrderedSame) {
[self presentAlertWithTitle:request.URL.host message:request.URL.query handler:nil];
return NO;
}
return YES;
}
- 原理:
- iOS與JS定義好
jsToOC協(xié)議, 用作JS調(diào)用OC時(shí)的url.scheme; - JS在按鈕點(diǎn)擊后加載URL:
jsToOc://loginSucceed?js_tokenString; -
UIWebView會(huì)在每次loadRequest時(shí), 調(diào)用回調(diào):- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; - 判斷request的
url.scheme是否是定義好的協(xié)議
- iOS與JS定義好
(2)、OC調(diào)用JS
邏輯: 點(diǎn)擊native的登錄按鈕, 將登錄成功后的數(shù)據(jù)回傳給JS
-
實(shí)現(xiàn): OC調(diào)用JS代碼, 并傳遞參數(shù)
JS代碼:
<div id = "returnValue" style = "font-size: 40px; border: 1px dotted; height: 100px;">
</div>
function OCTransferJS(action, params) {
<!-- alert('JS代碼被OC調(diào)用了' + action + params);-->
document.getElementById("returnValue").innerHTML = 'JS代碼被OC調(diào)用, 并向JS傳遞了參數(shù)' + action + '?' + params;
}
- OC代碼:
#pragma mark - OC調(diào)用JS OC調(diào)用JS代碼, 并傳遞參數(shù)
//!< 點(diǎn)擊native按鈕登錄, 并將登錄成功的參數(shù)回傳給JS
- (void)login:(UIButton *)sender {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSString *jsString = [NSString stringWithFormat:@"OCTransferJS('loginSucceed', 'oc_tokenString')"];
[self.webView stringByEvaluatingJavaScriptFromString:jsString];
// JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// [context evaluateScript:[NSString stringWithFormat:@"OCTransferJS('login_success', 'login_success_token')"]];
});
}
- 原理:
- iOS與JS約定好
OCTransferJS方法, 用作入口方法 - OC 使用
- stringByEvaluatingJavaScriptFromString方法訪問(wèn)JS - OC在登錄成功后將參數(shù)回傳給JS
- iOS與JS約定好
- stringByEvaluatingJavaScriptFromString只有在整個(gè)webViwe加載完畢后才會(huì)執(zhí)行
二、使用JavaScriptCore框架
通過(guò)協(xié)議攔截的方式, 可以進(jìn)行OC與JS之間的交互, 但是太過(guò)繁瑣了.
Apple在iOS7中提供了一個(gè)功能強(qiáng)大的框架:JavaScriptCore, 簡(jiǎn)化了UIWebView與OC的交互
(1)、JS調(diào)用OC
邏輯: 點(diǎn)擊HTML中的登錄按鈕, 傳遞登錄成功的參數(shù)給OC, OC展示登錄成功的參數(shù)
-
實(shí)現(xiàn):JS登錄成功將參數(shù)傳遞給OC.gif
JS代碼:
<button onclick = "login()" style = "font-size: 40px;">登錄</button>
function login() {
var token = "js_tokenString";
loginSucceed(token);
}
//!< 登錄成功
function loginSucceed(token) {
var action = "loginSucceed";
JSTransferOC(action, token);
}
//!< JS調(diào)用OC入口
function JSTransferOC(action, params) {
var url = "JSTransferOC://" + action + "?" + params;
loadURL(url);
}
//!< 加載URL
function loadURL(url) {
window.location.href = url;
}
- OC代碼:
#import <JavaScriptCore/JavaScriptCore.h>
- (void)webViewDidFinishLoad:(UIWebView *)webView {
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
context[@"JSTransferOC"] = ^(NSString *action, NSString *params) {
[self presentAlertWithTitle:[NSString stringWithFormat:@"獲取到點(diǎn)擊js按鈕的事件, 傳遞過(guò)來(lái)了事件:%@" , action] message:params handler:nil];
};
}
- 原理:
- JS和OC定義好一個(gè)入口函數(shù)
JSTransferOC - OC通過(guò)
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];獲取JSContext上下文 - JS調(diào)用
JSTransferOC函數(shù), OC以Block形式監(jiān)聽(tīng)此方法,context[@"JSTransferOC"] = ^() {}, 收到JS的參數(shù)回調(diào)
- JS和OC定義好一個(gè)入口函數(shù)
Tips: 以
Block形式監(jiān)聽(tīng)函數(shù)時(shí), 不要在Block內(nèi)部使用JSValue或者JSContext, 因?yàn)?code>JSContext強(qiáng)引用Block,Block強(qiáng)引用外部變量.JSValue需要JSContext來(lái)執(zhí)行JS代碼, 故而JSVaule又強(qiáng)引用JSContext, 會(huì)形成循環(huán)引用. 因?yàn)镴S中沒(méi)有弱引用的概念, 所以__weak沒(méi)有作用. 可以用將JSValue作為Block內(nèi)部變量的方式 和[JSContext currentContext]的方式來(lái)解決兩種循環(huán)引用
(2)、OC調(diào)用JS
邏輯:點(diǎn)擊原生的登錄按鈕, 將登錄成功的數(shù)據(jù)傳遞給JS, JS進(jìn)行展示
-
實(shí)現(xiàn): OC調(diào)用JS
JS代碼:
<div id = "returnValue" style = "font-size: 40px; border: 1px dotted; height: 100px;">
</div>
function OCTransferJS(action, params) {
document.getElementById("returnValue").innerHTML = 'JS代碼被OC調(diào)用, 并向JS傳遞了參數(shù)' + action + '?' + params;
}
- OC代碼:
#pragma mark - OC調(diào)用JS OC調(diào)用JS代碼, 并傳遞參數(shù)
//!< 點(diǎn)擊native按鈕登錄, 并將登錄成功的參數(shù)回傳給JS
- (void)login:(UIButton *)sender {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//!< 方式二: 使用JavaScriptCore
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
[context evaluateScript:[NSString stringWithFormat:@"OCTransferJS('login_success', 'login_success_token')"]];
});
}
- 原理:
- JS和iOS預(yù)定好入口函數(shù):
OCTransferJS - OC通過(guò)
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];方式來(lái)獲取JS上下文JSContext; -
JSContext通過(guò)evaluateScript函數(shù)執(zhí)行JS代碼OCTransferJS('login_success', 'login_success_token');
- JS和iOS預(yù)定好入口函數(shù):
PS: 筆者在項(xiàng)目中遇到過(guò)傳遞對(duì)象的情況,將
NSDictionary轉(zhuǎn)換成JSON字符串包裝在參數(shù)中, 類似這種[NSString stringWithFormat:@"OCTransferJS('%@')", resustJsonStr], 不過(guò)不能成功傳遞. 推測(cè)可能是這樣調(diào)用的話, JS不能識(shí)別, 還望有知道答案的大牛不吝賜教~
三、JSExport協(xié)議
JSExport是JavaScriptCore框架中的一個(gè)協(xié)議. 如果一個(gè)對(duì)象遵循了JSExport協(xié)議, 那么該對(duì)象的方法會(huì)對(duì)JS開(kāi)放, 允許JS直接調(diào)用;
(1)、JS調(diào)用OC
邏輯: JS中點(diǎn)擊按鈕, 調(diào)用OC對(duì)象中的方法, 并傳遞參數(shù).
-
實(shí)現(xiàn): JS調(diào)用OC本地代碼,并傳遞參數(shù)
JS代碼:
<div style="height: 200px; margin-top: 20px">
<button onclick = "clickbtn()" style = "font-size: 40px; width: 400px; height: 100px" >
js調(diào)用OC代碼
</button>
</div>
function clickbtn() {
var str = window.iOSDelgate.onClick("點(diǎn)擊了button", "調(diào)用成功");
alert(str);
}
- OC代碼:
//!< 定義protocol
@protocol JSObjectDelegate <JSExport>
//!< 取一個(gè)JS能夠識(shí)別的名字
JSExportAs(onClick, - (NSString *)onClick:(NSString *)param1 param2:(NSString *)param2);
@end
//!< 遵循代理 讓JS能夠調(diào)用到方法
@interface UIWebViewVC : UIViewController<JSObjectDelegate, UIWebViewDelegate>
@end
- (void)webViewDidFinishLoad:(UIWebView *)webView {
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//!< 將JS和OC中定義好的類(iOSDelgate)指向自身, 這樣JS中的 window.iOSDelgate.onClick();就會(huì)調(diào)用oc代碼
context[@"iOSDelgate"] = self;
}
#pragma mark - 實(shí)現(xiàn)protocol方法, 供JS調(diào)用, JS調(diào)用OC本地代碼, 常用在定位, 獲取相冊(cè)等操作
//!< 點(diǎn)擊HTML中的按鈕, 調(diào)用OC中的代碼, 并回傳參數(shù)給JS
- (NSString *)onClick:(NSString *)param1 param2:(NSString *)param2 {
return [NSString stringWithFormat:@"OC方法被JS調(diào)用%@%@", param1, param2];
}
- 原理:
- JS和OC定義好入口函數(shù)
onClick和iOSDelgate類; - OC創(chuàng)建遵循
JSExport協(xié)議的JSObjectDelegate協(xié)議, 并在協(xié)議中聲明- onClick: param2:方法; -
JSExportAs()將OC方法重命名, 以便JS能夠識(shí)別; - OC類遵循
JSExport協(xié)議, 并實(shí)現(xiàn)協(xié)議方法- onClick: param2:; - JS調(diào)用入口函數(shù)
onClick, 并傳遞參數(shù); - OC獲取參數(shù), 經(jīng)過(guò)處理, 再次回傳參數(shù)給JS, JS進(jìn)行展示;
- JS和OC定義好入口函數(shù)
Tips: 若OC方法沒(méi)有參數(shù), 則不需要
JSExportAs()進(jìn)行重命名;
(2)、OC調(diào)用JS:
與上述方法一致.




