iOS发展至今, 各种hybird方案层出不穷. 最经典的hybird方案莫过于在native框架中, 使用UIWebView
来展示HTML页面. 那么我们需要去考虑怎么与web前端进行交互. 最近项目中有与web前端进行交互, 遇到了一些坑点, 故而在这里整理下UIWebView
和iOS进行交互的方案.
一、协议拦截
(1)、JS调用OC:
逻辑: 点击JS中的button, 传递一个参数给OC, 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每次加载请求前都会调用这个方法
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
//!< 通过判断URL.scheme来判断是否是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
协议, 用作JS调用OC时的url.scheme
; - JS在按钮点击后加载URL:
jsToOc://loginSucceed?js_tokenString
; -
UIWebView
会在每次loadRequest时, 调用回调:- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
; - 判断request的
url.scheme
是否是定义好的协议
- iOS与JS定义好
(2)、OC调用JS
逻辑: 点击native的登录按钮, 将登录成功后的数据回传给JS
-
实现:
JS代码:
<div id = "returnValue" style = "font-size: 40px; border: 1px dotted; height: 100px;">
</div>
function OCTransferJS(action, params) {
<!-- alert('JS代码被OC调用了' + action + params);-->
document.getElementById("returnValue").innerHTML = 'JS代码被OC调用, 并向JS传递了参数' + action + '?' + params;
}
- OC代码:
#pragma mark - OC调用JS OC调用JS代码, 并传递参数
//!< 点击native按钮登录, 并将登录成功的参数回传给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
方法访问JS - OC在登录成功后将参数回传给JS
- iOS与JS约定好
- stringByEvaluatingJavaScriptFromString
只有在整个webViwe
加载完毕后才会执行
二、使用JavaScriptCore框架
通过协议拦截的方式, 可以进行OC与JS之间的交互, 但是太过繁琐了.
Apple在iOS7
中提供了一个功能强大的框架:JavaScriptCore
, 简化了UIWebView
与OC的交互
(1)、JS调用OC
逻辑: 点击HTML中的登录按钮, 传递登录成功的参数给OC, OC展示登录成功的参数
-
实现:
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调用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:@"获取到点击js按钮的事件, 传递过来了事件:%@" , action] message:params handler:nil];
};
}
- 原理:
- JS和OC定义好一个入口函数
JSTransferOC
- OC通过
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
获取JSContext上下文 - JS调用
JSTransferOC
函数, OC以Block
形式监听此方法,context[@"JSTransferOC"] = ^() {}
, 收到JS的参数回调
- JS和OC定义好一个入口函数
Tips: 以
Block
形式监听函数时, 不要在Block内部使用JSValue
或者JSContext
, 因为JSContext
强引用Block
,Block
强引用外部变量.JSValue
需要JSContext
来执行JS代码, 故而JSVaule
又强引用JSContext
, 会形成循环引用. 因为JS中没有弱引用的概念, 所以__weak
没有作用. 可以用将JSValue
作为Block
内部变量的方式 和[JSContext currentContext]
的方式来解决两种循环引用
(2)、OC调用JS
逻辑:点击原生的登录按钮, 将登录成功的数据传递给JS, 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调用, 并向JS传递了参数' + action + '?' + params;
}
- OC代码:
#pragma mark - OC调用JS OC调用JS代码, 并传递参数
//!< 点击native按钮登录, 并将登录成功的参数回传给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预定好入口函数:
OCTransferJS
- OC通过
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
方式来获取JS上下文JSContext; -
JSContext
通过evaluateScript
函数执行JS代码OCTransferJS('login_success', 'login_success_token')
;
- JS和iOS预定好入口函数:
PS: 笔者在项目中遇到过传递对象的情况,将
NSDictionary
转换成JSON字符串
包装在参数中, 类似这种[NSString stringWithFormat:@"OCTransferJS('%@')", resustJsonStr]
, 不过不能成功传递. 推测可能是这样调用的话, JS不能识别, 还望有知道答案的大牛不吝赐教~
三、JSExport协议
JSExport
是JavaScriptCore
框架中的一个协议. 如果一个对象遵循了JSExport
协议, 那么该对象的方法会对JS开放, 允许JS直接调用;
(1)、JS调用OC
逻辑: JS中点击按钮, 调用OC对象中的方法, 并传递参数.
-
实现:
JS代码:
<div style="height: 200px; margin-top: 20px">
<button onclick = "clickbtn()" style = "font-size: 40px; width: 400px; height: 100px" >
js调用OC代码
</button>
</div>
function clickbtn() {
var str = window.iOSDelgate.onClick("点击了button", "调用成功");
alert(str);
}
- OC代码:
//!< 定义protocol
@protocol JSObjectDelegate <JSExport>
//!< 取一个JS能够识别的名字
JSExportAs(onClick, - (NSString *)onClick:(NSString *)param1 param2:(NSString *)param2);
@end
//!< 遵循代理 让JS能够调用到方法
@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();就会调用oc代码
context[@"iOSDelgate"] = self;
}
#pragma mark - 实现protocol方法, 供JS调用, JS调用OC本地代码, 常用在定位, 获取相册等操作
//!< 点击HTML中的按钮, 调用OC中的代码, 并回传参数给JS
- (NSString *)onClick:(NSString *)param1 param2:(NSString *)param2 {
return [NSString stringWithFormat:@"OC方法被JS调用%@%@", param1, param2];
}
- 原理:
- JS和OC定义好入口函数
onClick
和iOSDelgate
类; - OC创建遵循
JSExport
协议的JSObjectDelegate
协议, 并在协议中声明- onClick: param2:
方法; -
JSExportAs()
将OC方法重命名, 以便JS能够识别; - OC类遵循
JSExport
协议, 并实现协议方法- onClick: param2:
; - JS调用入口函数
onClick
, 并传递参数; - OC获取参数, 经过处理, 再次回传参数给JS, JS进行展示;
- JS和OC定义好入口函数
Tips: 若OC方法没有参数, 则不需要
JSExportAs()
进行重命名;
(2)、OC调用JS:
与上述方法一致.