iOS与JS交互总结
近几年来移动开发使用网页嵌入形式的越来越多,这就不可避免的出现原生控件和网页页面的JS交互,本篇就大概总结一下目前iOS开发中原生控件与JS的交互的几种形式。</br>
iOS开发中使用的是UIWebView控件来加载网页页面资源的。所以我们也就主要围绕这个控件来总结一下,大体上可以分为三种形式:
1、原生api交互,直接执行脚本,拦截代理
2、第三方库WebViewJavascriptBrige交互,实质还是拦截代理
3、JavaScriptCore框架
3.1 context上下文,context上下文直接设置和调用
3.2 JSExport协议,通过JSExport协议设置和调用
接下来就一个一个的来看看到底是如何实现的,大家也可以提前把demo下载下来,结合代码来看效果会更好哦。
原生api交互
使用原生API来实现交互,实际上主要用到一个函数和一个协议,
函数:
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
协议:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
1、 OBJC调用JS
直接使用UIWebView的函数执行JS代码,
比如我们JS代码中有一个函数objcCallJS
,
function objcCallJS() {
var data = 'ljt'
alert('来自objc的调用,有返回值:' + data);
return data
}
那我们该如何去调用呢?
直接在相应的地方如此调用即可:
NSString *returnStr = [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJS()"];
NSLog(@"返回值为:%@",returnStr);
我们注意到这个地方会有个返回值,此时如果我们调用的JS代码有返回值得话,就会赋值到returnStr
这个变量,可供我们后续使用。如果JS代码没有返回值得话,这个值默认是Undefine。
OBJC调用JS代码就是如此的简单。这个时候,有人说了,如果我们想向JS代码中传值该怎么弄呢?
JS代码:
function objcCallJSParam(param1,param2) {
alert('来自objc的调用,带有参数:' + param1 + ' ' + param2);
}
OBJC代码:
- (void)callJSBtnParamAction:(id)sender {
NSLog(@"开始调用JS函数,带有参数");
[self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJSParam('ljt','ths')"];
}
这样我们就可以向JS代码中传入参数了。
2、 JS调用OBJC
在这里我们使用拦截UIWebView的代码来实现,我们知道代理函数- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
会在UIwebView开始加载页面的时候调用,如果我们返回NO则这个页面什么也不做,如果返回YES则加载这个页面。有了这个特性,我们就可以来实现JS调用OBJC的代码。
</br>具体的流程: 页面响应时,重新设置页面的href
,并将OBJC所用的参数以及所调用的指定函数,封装成一个特定格式的URL链接,然后我们在UIwebView的协议中拦截这个链接,然后解析出来,根据固定的格式做出相应的处理。
具体来说:当我们页面中一个事件,设置了页面的href
,携带了两个参数,
//调用OBJC
function JSCallObjc() {
window.location.href="www.baidu.com/param=ljt&ths";
}
我们在OBJC中可以这么做:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSLog(@"将要开始加载页面");
NSString *urlStr = [[request URL] absoluteString];
if ([urlStr containsString:@"param"]) {
NSRange range = [urlStr rangeOfString:@"param="];
NSString *paramStr = [urlStr substringFromIndex:range.location + range.length];
NSLog(@"param=%@",paramStr);
UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc弹框" message:paramStr preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alterVC addAction:okAction];
[self presentViewController:alterVC animated:YES completion:nil];
return NO;
}
return YES;
}
这样就实现了JS调用OBJC的效果,当然这个例子不是那么好,href的格式我是随便写写,当真正使用起来的时候需要事先设计好合理的封装方式。
</br></br>
第三方库WebViewJavascriptBrige交互
这个部分我们使用有名的第三方库WebViewJavascriptBrige
来介绍一下,关于这个库的实现,网上已经有很多介绍,关于原理的实现就不多介绍了,直接进入使用环节,使用这个库的时候有一个地方需要注意一下,在所加载的页面中需要实现固定的写法:
//固定函数,必须这样写
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)
}
//所有交互的函数都写在这里面
setupWebViewJavascriptBridge(function(bridge) {
····
})
1、 OBJC调用JS
首先我们需要在JS中注册一个函数,供OBJC来调用
bridge.registerHandler('objcCallJS', function(data, responseCallback) {
myAlert(data)
var responseData = { 'Javascript Says':'Right back atcha!' }
responseCallback(responseData)
})
这样注册之后,我们就可以在OBJC代码中调用这个名为objcCallJS
的函数了。
OBJC中的代码:
- (void)callJSBtnAction:(id)sender {
NSLog(@"调用JS函数");
//callHandler有几种形式
//- (void)callHandler:(NSString *)handlerName 只调用函数
//- (void)callHandler:(NSString *)handlerName data:(id)data 调用的同时携带数据
//- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback 不但调用和携带数据,而且设置回调函数处理所需的数据(如果需要处理结果数据)
// [self.bridge callHandler:@"objcCallJS" data:@{@"key":@"value"} responseCallback:^(id responseData){
// NSLog(@"%@",responseData);
// }];
// [self.bridge callHandler:@"objcCallJS"];
[self.bridge callHandler:@"objcCallJS" data:@{@"key":@"value"}];
}
这个地方有几处我们需要说明的地方
1、objcCallJS
这个名称必须是一样的
2、OBJC中调用JS的函数是通过- (void)callHandler:(NSString*)handlerName data:(id)data;
这个借口调用的,这个接口的最终调用实现是- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;
,当我们调用的时候,<strong>handlerName</strong>对应JS代码中注册的函数名,<strong>data</strong>对应JS代码中的data,即为OBJC向JS中传入的参数,还有一个有意思的参数就是<strong>responseCallback</strong>,就是JS代码的responseCallback,也就是说,当我们的JS代码处理完成之后,OBJC还有一次机会可以对JS代码处理完成的逻辑进行处理。当然data和responseCallback这两个参数都不是必须的。
2、 JS调用OBJC
与OBJC调用JS的逻辑类似,
首先我们需要在OBJC中注册JS能够调用的函数:
//注册js调用函数,并设定回调。js中可以调用JSCallObjc的函数
[self.bridge registerHandler:@"JSCallObjc" handler:^(id data, WVJBResponseCallback responseCallback){
NSString *paramStr = [data objectForKey:@"key"];
UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc弹框" message:paramStr preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alterVC addAction:okAction];
[self presentViewController:alterVC animated:YES completion:nil];
responseCallback(@"Response from testObjcCallback");
}];
注册完成以后,我们就可以在JS代码中调用这个注册好的函数了:
//调用OBJC中的函数
var callbackButton = document.getElementById('btn')
callbackButton.onclick = function(e) {
e.preventDefault()
//callHandler的参数可变
//bridge.callHandler('JSCallObjc') 没有携带数据
//bridge.callHandler('JSCallObjc', {'key': 'value'}) 携带数据
//bridge.callHandler('JSCallObjc', {'key': 'value'},function(response) {
// log('JS got response', response)
// }) 携带参数和回调函数
//bridge.callHandler('JSCallObjc')
bridge.callHandler('JSCallObjc',{'key': 'value'})
}
我们可以看到,这个逻辑和接口与OBJC调用JS的逻辑和接口大同小异,具体的参数也是差不多的。
到此我们就可以利用这个第三方库来实现OBJC和JS的互相调用。其实这个库的内部还是通过拦截协议来实现这个交互过程的。
JavaScriptCore框架
JavaScriptCore框架是在iOS7之后引入的框架,这个框架在JS交互上为我们提供了很大帮助,可以在html界面上调用OC方法并传参,也可以在OC上调用JS方法并传参。关于JavaScriptCore的详细介绍和注意事项,请大家自行google之。这里我们只是简单介绍一下用法,很浅显,请大家不要见怪。
使用之前,我们需要导入JavaScriptCore.framework这个框架
context上下文,context上下文直接设置和调用
1、 OBJC调用JS
OBJC调用JS之前,需要获取一下JS的上下文,
self.context = [self.homeWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSLog(@"self.contex=%@",self.context);
获取到这个上下文之后,我们就可以对JS的执行环境做处理了,假如我们需要调用JS中已经实现过的函数,我们可以直接调用相应的函数就行:
- (void)callJSBtnAction:(id)sender {
NSLog(@"开始调用JS函数,有返回值");
//第一种
// NSString *returnStr = [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJS()"];
// NSLog(@"返回值为:%@",returnStr);
//第二种
// NSString *js = @"objcCallJS()";
// JSValue *value = [self.context evaluateScript:js];
// NSLog(@"%@",[value toString]);
//第三种
JSValue *valueFuc = self.context[@"objcCallJS"];
JSValue *value = [valueFuc callWithArguments:nil];
NSLog(@"%@",[value toString]);
}
以上代码中我们展示了三种不同的调用方式,特别说明的是第三种,首先我们从上下文中获取要执行的函数,并把它保存在一个JSValue变量中JSValue *valueFuc = self.context[@"objcCallJS"];
,然后对这个变量调用其函数- (JSValue *)callWithArguments:(NSArray *)arguments;
传入参数。
我们可以在JS中这样使用:
因为我们传入的参数为nil,所以我们在JS中没有获取参数,代码demo中我们提供了获取参数的版本,具体的可以参照代码demo,
function objcCallJS() {
var data = 'ljt'
alert('来自objc的调用,有返回值:' + data);
return data
}
2、 JS调用OBJC
首先我们在OBJC中,增加JS可以执行的代码,怎么添加呢?还是需要用到上面所提到的上下文context
。
加入我们需要实现一个可以接收参数的函数名为JSCallObjcParam
,该怎么写呢?
//有参数
__weak typeof(self) weakSelf = self;
self.context[@"JSCallObjcParam"] = (id)^(NSString *param1, NSString *param2) {
NSLog(@"有参,无返回值");
NSString *message = [NSString stringWithFormat:@"%@:%@",param1,param2];
UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc弹框" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alterVC addAction:okAction];
[weakSelf presentViewController:alterVC animated:YES completion:nil];
};
这样我们就实现了一个可以在JS中调用的函数JSCallObjcParam
,这个函数就收两个参数。
我们在JS中这样调用:
//调用OBJC
function callObjcParam(param1,param2) {
var data = JSCallObjcParam('ljt','ths')
alert('来自objc(callObjcParam)的返回值:' + data);
}
这么两步我们就实现了iOS和JS的相互调用。</br>
demo中我们分别实现了相互调用的传参和不传参,有返回值和没有返回值的情况,具体的可以看demo代码。
JSExport协议,通过JSExport协议设置和调用
JSExport是一个协议,我们可以自定义一个协议,继承此协议,在协议中声明可以在JS中使用的API函数。
首先我们实现一个这么样的协议ObjcJSDelegate
@protocol ObjcJSDelegate <JSExport>
//有参数
- (void)JSCallObjcParam:(NSString *)param1 with:(NSString *)param2;
//无参数
- (void)JSCallObjc;
//有返回值
- (NSString *)JSCallObjcReturn;
@end
<strong>在这里我们需要知道的是,这个协议中所声明的接口是供JS调用的,也就是说JS代码调用OBJC的相关接口,可以放在这个协议中。而OBJC调用JS还是通过上下文来直接调用的</strong>
其次,我们需要一个实现了这个代理的类,我们就直接实现在@interface JSExportViewController ()<ObjcJSDelegate>
UIWebView所在的ViewController中:
#pragma mark ObjcJSDelegate
- (void)JSCallObjcParam:(NSString *)param1 with:(NSString *)param2 {
NSLog(@"有参,无返回值");
NSString *message = [NSString stringWithFormat:@"%@:%@",param1,param2];
UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc弹框" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alterVC addAction:okAction];
[self presentViewController:alterVC animated:YES completion:nil];
}
- (void)JSCallObjc {
NSLog(@"无参,无返回值");
}
- (NSString *)JSCallObjcReturn {
NSLog(@"无参,有返回值");
return @"ljt";
}
然后我们需要让JS知道如何调用我们所需要的函数,这个时候同样需要前面一直提到的上下文:
self.context[@"OBJCFuc"] = self;
如此,我们就可以在JS中通过OBJCFuc
(OBJCFuc.接口名字)来调用我们之前协议实现的相关接口了:
JS调用
//调用OBJC
function callObjcParam(param1,param2) {
var data = OBJCFuc.JSCallObjcParamWith('ljt','ths')
alert('来自objc(callObjcParam)的返回值:' + data);
}
function callObjc() {
var data = OBJCFuc.JSCallObjc()
alert('来自objc(callObjc)的返回值:' + data);
}
function callObjcReturn() {
var data = OBJCFuc.JSCallObjcReturn()
alert('来自objc(callObjcReturn)的返回值:' + data);
}
这样就实现了JS调用OJBC。
这篇文章,只是简单的介绍了iOS和JS的交互的几种方法,很粗浅,很简单,但也许很实用,最后附上代码demo