方案一:传统的交互
优点:不需要等待页面加载完才触发,当相应的代码被运行就能调用OC的方法(相比 JavaScriptCore而言)
缺点:需要繁琐地解释字符串得到相应的方法名和传值,且调用的方法也不能传递返回值;
1、OC调用JS
使用stringByEvaluatingJavaScriptFromString
方法,可以将javascript嵌入页面中。不过需要等UIWebView中的页面加载完成之后去调用。
- (void)viewDidLoad{
[super viewDidLoad];
webview.backgroundColor = [UIColor clearColor];
webview.scalesPageToFit =YES;
webview.delegate =self;
NSURL *url =[[NSURL alloc] initWithString:@"https://www.google.com.hk"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
[webview loadRequest:request];
}
//1、OC中调用JS的对象,在webViewDidFinishLoad方法中就可以通过javascript操作界面元素
- (void)webViewDidFinishLoad:(UIWebView *)webView {
//1.1、获取当前页面的url
NSString *currentURL = [webView stringByEvaluatingJavaScriptFromString:@"document.location.href"];
//1.2、获取当前页面的title
NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"];
//1.3、修改界面元素的值(这样就实现了在google搜索关键字:“Terence”的功能。)
NSString *js_result = [webView stringByEvaluatingJavaScriptFromString:@"document.getElementsByName('q')[0].value='Terence';"];
//1.4、表单提交
NSString *js_result2 = [webView stringByEvaluatingJavaScriptFromString:@"document.forms[0].submit(); "];
//1.5、插入js代码(上面的功能我们可以封装到一个js函数中,将这个函数插入到页面上执行,代码如下:)
// a、首先通过js创建一个script的标签,type为'text/javascript'。
// b、然后在这个标签中插入一段字符串,这段字符串就是一个函数:myFunction,这个函数实现google自动搜索关键字的功能。
// c、然后使用stringByEvaluatingJavaScriptFromString执行myFunction函数。
[webView stringByEvaluatingJavaScriptFromString:
@"var script = document.createElement('script');"
"script.type = 'text/javascript';"
"script.text = \"function myFunction() { "
"var field = document.getElementsByName('q')[0];"
"field.value='Terence';"
"document.forms[0].submit();"
"}\";"
"document.getElementsByTagName('head')[0].appendChild(script);"//添加到head标签
];
[webView stringByEvaluatingJavaScriptFromString:@"myFunction();"];
//2.1、调用JS的函数
[webView stringByEvaluatingJavaScriptFromString:@"clickme();"];
//2、OC中调用JS的方法带参数
NSString *value = @“把我显示出来”;
[self.webView stringByEvaluatingJavaScriptFromString:@“transValue(‘%@‘)”,value];
}
<html>
<head>
<meta xmlns="http://www.w3.org/1999/xhtml" http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>这是一个html示例文件</title>
<script Type='text/javascript'>
function clickme() {
alert('点击按钮了!');
}
function transValue(insert) {
alert(insert);
}
</script>
</head>
<body>
<h1>OC与JS互动</h1>
</body>
</html>
2、JS调用OC
webView拦截url链接,获取自定义协议内容,再处理结果
//JS 函数
function sendToOC(){
var para1 = document.getElementById("element1").value;
var para2 = document.getElementById("element2").value;
// "gallery://"为自定义协议头;para1¶2为要传给OC的值,以","作为分隔
var url = "gallery://"+para1+","+para2;
document.loc ation = url;
}
//遵守UIWebViewDelegate代理协议。
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
//拿到网页的实时url
NSString *requestStr = [[request.URL absoluteString] stringByRemovingPercentEncoding];
//在url中寻找自定义协议头"gallery://"
if ([requestStr hasPrefix:@"gallery://"]) {
// 以"://"为中心将url分割成两部分,放进数组arr
NSArray *arr = [requestStr componentsSeparatedByString:@"://"];
//取其后半段的参数值
NSString *paramStr = arr[1];
//以","为标识将后半段url分割成若干部分,放进数组
NSArray *paraArray = [paramStr componentsSeparatedByString:@","];
//取出参数,进行使用
if (paraArray.count) {
NSLog(@"有参数");
[self doSomeThingWithParamA:paraArray[0] andParamB:paraArray[1]];
}else{
NSLog(@"无参数");
}
return NO;
}
return YES;
}
//对JS传来的值进行处理
- (void)doSomeThingWithParamA:(id)paramA andParamB:(id)paramB{
NSLog(@"para1:%@--para2:%@", paramA, paramB);
}
方案二:JavaScriptCore.framework
框架
优点:苹果官方框架,简单,高效;
缺点:OC调用JS,必须等HTML加载完成之后才可以
1、OC调用JS
//网页加载完成调用此方法
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
//首先创建JSContext 对象(此处通过当前webView的键获取到jscontext)
JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//准备执行的js代码
NSString *alertJS=@"alert('test')";
//通过oc方法调用js的alert(在webview加载结束,注入一段js代码)
[context evaluateScript:alertJS];
}
2、JS调用OC
#import <JavaScriptCore/JavaScriptCore.h>
-(void)webViewDidFinishLoad:(UIWebView *)webView{
// 1.创建实例
JSContext *jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 2.关联异常
jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
NSLog(@"异常信息:%@", exceptionValue);
};
// 3.获取参数(sendFunc,sendParam是在JS定好的方法名)
//遍历参数
jsContext[@"sendFunc"] = ^{
NSArray *args = [JSContext currentArguments];
for (id obj in args){
NSLog("parameter:%@",obj);
}
}
//或者直接传入参数
jsContext[@"sendParam"] = ^(NSDictionary *dic){
NSLog("params:%@",dic);
}
}
//其中jsendToOC是指js的函数名,得到args数组里面为js函数的参数,即js要传给oc的参数。
js 代码:
function onClick(){
var para1 = document.getElementById("element1").value;
var para2 = document.getElementById("element2").value;
sendFunc(para1,para2);
}
//第二种方法
<input type="button" value="测试" onclick="sendParam({'paramKey':'paramValue'})" />
在需要传值给OC的函数里,例如上面的click函数,直接调用sendToOC函数即可。
方案三:WebViewJavascriptBridge
第三方框架(github地址)
WebViewJavascriptBridge同时支持UIWeView和WKWebView,无论是JS调用OC还是OC调用JS,都可以正常传值和返回值;而且在页面加载时只要JS代码被运行就可以进行交互,不会遇到上面两种方案的缺点,所以是现在处理交互的主流做法。
<!-- 申明交互 -->
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback];
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
<!-- 处理交互 方法名要和ios内定义的对应-->
setupWebViewJavascriptBridge(function (bridge){
//OC传值给JS 'wevViewJSHandler'为双方自定义好的统一方法名;'data'是OC传过来的值;'responseCallback'是JS接收到之后给OC的回调
<!--处理 oc 调用 js -->
bridge.registerHandler('webViewJSHandler', function(data, responseCallback) {
//打印OC传过来的值
log('ObjC called wevViewJSHandler with', data)
var responseData = { 'Javascript Says':'Right back atcha!' }
log('JS responding with', responseData)
//处理完,回调传值给oc
responseCallback(responseData)
})
var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
callbackButton.innerHTML = '点击我,我会调用oc的方法'
callbackButton.onclick = function(e) {
e.preventDefault()
<!--处理 js 调用 oc -->
bridge.callHandler('loginAction', {'userId':'zhangsan','name': '章三'}, function(response) {
//处理oc过来的回调
alert('收到oc过来的回调:'+response)
})
}
})
}
#immport "WebViewJavascriptBridge.h"
-(void)viewDidLoad{
[super viewDidLoad];
//1.初始化 WebViewJavascriptBridge
if (_bridge) { return; }
[WebViewJavascriptBridge enableLogging]; //设置第三方Bridge是否可用
_bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
[_bridge setWebViewDelegate:self]; //设置bridge代理
//请求加载html,注意:这里h5加载完,会自动执行一个调用oc的方法
[self loadExamplePage:webView];
//申明js调用oc方法的处理事件,这里写了后,h5那边只要请求了,oc内部就会响应
[self JS2OC];
//模拟操作:2秒后,oc会调用js的方法
//注意:这里厉害的是,我们不需要等待html加载完成,就能处理oc的请求事件;此外,webview的request 也可以在这个请求后面执行(可以把上面的[self loadExamplePage:webView]放到[self OC2JS]后面执行,结果是一样的)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self OC2JS];
});
}
//JS 调用 OC
-(void)JS2OC{
/*
含义:JS调用OC
@param registerHandler 要注册的事件名称(比如这里我们为loginAction)
@param handel 回调block函数 当后台触发这个事件的时候会执行block里面的代码
*/
[_bridge registerHandler:@"loginAction" handler:^(id data, WVJBResponseCallback responseCallback) {
// data js页面传过来的参数 假设这里是用户名和姓名,字典格式
NSLog(@"JS调用OC,并传值过来");
// 利用data参数处理自己的逻辑
NSDictionary *dict = (NSDictionary *)data;
NSString *str = [NSString stringWithFormat:@"用户名:%@ 姓名:%@",dict[@"userId"],dict[@"name"]];
[self renderButtons:str];
// responseCallback 给js的回复
responseCallback(@"报告,oc已收到js的请求");
}];
}
//OC 调用 JS
-(void)OC2JS{
/*
含义:OC调用JS
@param callHandler 商定的事件名称,用来调用网页里面相应的事件实现
@param data id类型,相当于我们函数中的参数,向网页传递函数执行需要的参数
注意,这里callHandler分3种,根据需不需要传参数和需不需要后台返回执行结果来决定用哪个
*/
//[_bridge callHandler:@"registerAction" data:@"我是oc请求js的参数"];
[_bridge callHandler:@"registerAction" data:@"uid:123 pwd:123" responseCallback:^(id responseData) {
NSLog(@"oc请求js后接受的回调结果:%@",responseData);
}];
}
关键点就是:OC和JS商定的方法名要统一,两端要合作一下。