iOS中Active与JS的交互
CommentiOS原生代码和HTML中的JS进行交互有以下几种方法
- 拦截URL
- 使用JavaScriptCore
- 使用WKScriptMessageBridge
- 使用开源框架WebViewJavascriptBridge
这几种方法各有特点,其中 方法1、2、4可以进行iOS 、Android跨平台操作 方法3只支持iOS平台,如果想要使用1套js支持iOS Android两端,那么使用使用WKScriptMessageBridge可以排除在外
拦截URL
js调用原生代码
拦截url使用比较简单,和后端同事协商好URL协议,比如jsaction://openAlbum表示打开相册
实现UIWebView的代理方法:shouldStartLoadWithRequest:navigationType,在方法中对url进行拦截判断1
2
3
4
5
6
7- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
if ([request.URL.absoluteString hasPrefix:@"jsaction://openAlbum
//调用原生扫描二维码
return NO;
}
return YES;
}
HTML代码:1
<a href="jsaction://openAlbum">扫一扫(拦截url)</a>
原生调用js代码:
在打开相册后如果想反馈给HTML页面,可以执行UIWebView的stringByEvaluatingJavaScriptFromString:方法,或者WKWebView的 evaluateJavaScript:completionHandler:方法。1
[self.webView stringByEvaluatingJavaScriptFromString:@"openResult(‘成功打开相册')”];
拦截URL使用简单,js端只要约定好url协议就可以,但是缺点同样明显,代理方法中讲充斥大量判断URL的硬编码,约定好的连接都是写死的,而且通信是单向的,不支持return和callBack,只能做send操作,不能做get操作,所以拦截URL适用于业务相对简单的需求。
使用JavaScriptCore
JavaScriptCore只支持UIWebView,支持系统版本为iOS7+
通过 JSContext 获取 UIWebView 的 JS 执行上下文。
然后通过这个上下文,进行 OC & JS 的双端交互。
js调用原生
你需要创建一个对象继承自NSObject,并遵循JSExport协议,代理的方法和js的方法保持一致,.m中实现代理方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//JSObject.h
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol JSObjectDelegate <JSExport>
-(void)openAlbum:(NSString *)message;
@end
@interface JSObject : NSObject<JSObjectDelegate>
@property(nonatomic,weak) id<AppJSObjectDelegate> delegate;
@end
//JSObject.m
#import "AppJSObject.h"
@implementation JSObject
-(void)openAlbum:(NSString *)message{
[self.delegate openAlbum:message];
}
@end
1 | JS代码: |
在UIWebView加载完成的代理中把SObject实例对象类注入到JS中,那么在js中调用方法就会调用到原生JSObject实例对象中对应的方法了。1
2
3
4
5
6
7-(void)webViewDidFinishLoad:(UIWebView *)webView
{
JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
JSObject *jsObject = [JSObject new];
jsObject.delegate = self;
context[@"app"] = jsObject;
}
原生调用js
1 | JSContext *context=[_mainWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; |
JavaScriptCore 是跨平台的,你不需要在代理方法中添加大量的硬编码,支持双向的return,js调用原生方法可以拿到return,native调用js也可以拿到return,缺点就是JavaScriptCore只支持UIWebView不支持WKWebView,而UIWebView在内存优化,加载速度上都存在一些问题,所以使用JavaScriptCore的话只能舍弃WKWebView,而且只支持return,不支持callback,所以在异步调用的时候拿不动返回值。
WKScriptMessageBridge
我们之前说过,WKScriptMessageBridge无法做到跨平台,所以这里做一个简单介绍:
通过 userContentController 把需要观察的 JS 执行函数注册起来。
然后通过一个协议方法,将所有注册过的 JS 函数执行的参数传递到此协议方法中。
初始化WKWebView时,调用addScriptMessageHandler:name:方法,name为js中的方法名,如openAlbum:1
2
3
4
5
6
7- (void)setupWKWebView{
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.userContentController = [[WKUserContentController alloc] init];
[configuration.userContentController addScriptMessageHandler:self name:@"openAlbum"];
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
webView.UIDelegate = self;
}
js代码1
window.webkit.messageHandlers.openAlbum.postMessage()
实现WKScriptMessageHandler代理方法,当js调用openAlbum方法时,会回调此代理方法:1
2
3
4
5- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:@"openAlbum"]) {
//调用原生扫码
}
}
WebViewJavascriptBridge
WebViewJavascriptBridge是github上的开源库,用于UIWebView和WKWebView中原生代码和JS的交互,另外iOS和Android的WebViewJavascriptBridge在github上不是一个地址,但是实现思路和原理都是一样的,所以可以做到跨平台使用
实现原理
把 原生 的方法注册到桥梁中,让 JS 去调用。
把 JS 的方法注册在桥梁中,让 原生代码 去调用。
1.install JSBridge
pod ‘WebViewJavascriptBridge’, ‘~> 6.0’
2.导出头文件
1 | #import <WKWebViewJavascriptBridge.h> |
3.初始化WebView和JSBridge
1 | WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; |
4.在js文件中加入以下两个初始化函数
1 | function setupWebViewJavascriptBridge(callback) { |
接下来我们描述一个场景
1.HTML中加一个按钮,点击调用原生代码 ,随机修改一个colorView的背景颜色
2.原生页面添加一个按钮,点击调用js代码,用来修改HTML页面颜色
在原生代码中注册一个修改colorView背景颜色的block1
2
3
4[self.bridge registerHandler:@"colorClick" handler:^(id data, WVJBResponseCallback responseCallback) {
self.colorView.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256) / 255.0 green:arc4random_uniform(256) / 255.0 blue:arc4random_uniform(256) / 255.0 alpha:1.0];
responseCallback(@"颜色修改完毕!");
}];
在js文件里找到button的点击事件,添加以下代码1
2
3
4WebViewJavascriptBridge.callHandler('colorClick',function(dataFromOC) {
alert("JS 调用了 OC 注册的 colorClick 方法");
document.getElementById("returnValue").value = dataFromOC;
})
此时我们就完成了HTML中点击按钮,修改原生代码中colorView的背景颜色,接下来继续
在JS中添加一个function 用来修改body的背景颜色1
2
3
4
5
6setupWebViewJavascriptBridge(function(bridge){
bridge.registerHandler('changeBGColor',function(data,responseCallback){
document.body.style.backgroundColor = "orange";
document.getElementById("returnValue").value = data;
});
});
然后在原生页面的button点击事件中添加以下代码1
[self.bridge callHandler:@"changeBGColor" data:@“修改body背景颜色为橙色”];
现在我们就完成从js调用原生代码修改colorview的背景颜色,原生页面的button点击时修改了HTML的背景颜色。
关于内存泄漏
在页面退出时需要删除在JSBridge中注入的block,否则可能会导致页面无法释放,造成内存泄漏的问题1
2
3
4- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self.bridge removeHandler:@"colorClick"];
}
关于传参
在OC和JS相互调用时候支持传递参数和添加回调
OC调用JS
1 | [self.bridg callHandler:@"changeBGColor"]; |
#####JS调用OC1
2
3
4
5
6WebViewJavascriptBridge.callHandler('scanClick');
WebViewJavascriptBridge.callHandler('scanClick',"JS 参数");
WebViewJavascriptBridge.callHandler('scanClick',{data : "这是 JS 传递到 OC 的扫描数据"},function(dataFromOC){
alert("JS 调用了 OC 的扫描方法!");
document.getElementById("returnValue").value = dataFromOC;
});
到这里WebViewJavascriptBridge就介绍完了,可以看到使用起来也很简单,你可以到WebViewJavascriptBridge的github主页上查看更多的信息。
WebViewJavascriptBridge虽然是一个三方库,但是已经是一个很成熟原生与JS交互的解决方案,在他的主页上可以看到Facbook也在使用它,WebViewJavascriptBridge特点是跨品台的,支持双向callback,可以异步回调,这也是我们项目组在用的方案