我们在项目中不可避免的要使用到WebView,一般的用法就是WebView直接加载URL,做一些基本展示操作。但是对于一些特定的需求或逻辑,我们可能就需要WebView传递一些数据到Native,由Native来对数据做处理,比如有跨域限制或拦截WebView请求的需求时候。
备注:这里的Native是相对于JavaScript来说的,是指OC代码。
1. UIWebView
我们这里要做到双向交互,要求UIWebView加载的Html可调用Naitve函数,Native可调用Html函数。我在这里使用的是Apple官方的JavaScriptCore
库。
1.1 Native调用JavaScript
在Native中我们调用JS的函数,函数名为showAlert
OC代码:
- (void)webViewDidFinishLoad:(UIWebView *)webView{
NSString *result = [webView stringByEvaluatingJavaScriptFromString:@"showAlert('webViewDidFinishLoad')"];
NSLog(@"html返回给native的返回值是:%@",result);
}
对应的JS代码:
<script>
function showAlert (str) {
alert(str);
return 'html的返回值';
}
</script>
1.2 JavaScript调用Native
OC代码:
#import <CoreLocation/CoreLocation.h>
//这里最主要是创建一个JSContext
JSContext *context = [w valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//通过这个context可以将OC中函数添加到JavaScript的运行环境中
context[@"add"] = ^(NSInteger a, NSInteger b){
return a+b;
};
//这里就是向JavaScript添加了一个add函数,将JavaScript传入的两个参数和返回至调用的JavaScript中。
对应的JS代码:
<button onclick="alert(add(2,3))">这个是注入module</button>
当然也可以将JSBridage抽象出来,统一在WebView中进行初始化。这样可以在JS中通过一个对象统一掉用Native的函数,可以避免JavaScript函数与Native的OC函数名冲突.
在OC中这样写:
#import <Foundation/Foundation.h>
#import "JSBridgeModule.h"
#import "StringUtil.h"
#import <JavaScriptCore/JavaScriptCore.h>
@implementation JSBridgeModule
- (NSString *)getTime:(NSString *)str{
NSLog(@"getTime native log:%@",str);
return [StringUtil getCurrentTime];
}
- (void)initJSBridge:(JSContext *)jsContext{
[jsContext setObject:self forKeyedSubscript:@"bridge"];//这里将整个JSBridgeModule赋给了JavaScript运行环境,在JavaScript便可以使用bridge.函数名来调用Native函数
jsContext.exceptionHandler = ^(JSContext* context, JSValue* exceptionValue) {
context.exception = exceptionValue;
NSLog(@"js页面异常了:%@", exceptionValue);
};
}
- (void)callJSFunction:(JSContext *)jsContext{
JSValue *result = [jsContext evaluateScript:@"showAlert('这是native传递个html的数据')"];
NSLog(@"html返回给native的返回值是:%@",result);
}
@end
对应的JS使用方法
<button onclick="alert(bridge.getTime('这个是注入module'))">这个是注入module</button>
2. WKWebView
在iOS 8.0中更新了WKWebView来替代UIWebView,WKWebView的性能和效率比UIWebView要强。
这里有个插曲,,由于我们项目的需要,我们要对WebView加载的Html进行网络请求的拦截,只允许Html中的请求加载按照我们规范的地址。我对UIWebView和WKWebView分别作了研究,注册WebView的请求delegate是行不通的,我在UIWebView中重写URlSession来拦截所有的网络请求,这样可以达到拦截UIWebView请求目的,但是这个方法在WKWebView中行不通,因为WKWebView和当前项目是不同同一个进程中运行的,所以无法拦截WKWebView中的网络请求,所以这条路行不通。我会新写一篇文章详细的讲解这个问题。
下面言归正传,WKWebView中JS与Native的交互
2.1 Native调用JavaScript
这里很简单,和UIWebView差不多
OC代码:
//这是是调用JS的showAlert函数,并传入参数hello
[self.webView evaluateJavaScript:@"showAlert('hello')" completionHandler:nil];
JS代码:
<script>
function showAlert (str) {
alert(str);
return 'html的返回值';
}
</script>
2.2 JavaScript调用Native
OC代码:
向WebView中注入可供JS调用对象
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc]init];
WKUserContentController *controller = [[WKUserContentController alloc]init];
[controller addScriptMessageHandler:self name:@"bridge"];
//这里的bridge对象即为JS调用Native的桥接
config.userContentController = controller;
注册WebView delegate
//JavaScript与Native的交互
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
NSDictionary *body = message.body;
NSLog(@"userContentController message.body:%@",body);
//这里来判断JS传递过来的数据是否是需要的
if ([message.name isEqualToString:@"xxx"]) {
NSString *method = [body valueForKey:@"function"];
if ([method isEqualToString:@"nativeSetParam"]) {
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
[userDefault setValue:[body valueForKey:@"value"] forKey:[body valueForKey:@"key"]];
[userDefault synchronize];
}
}
}
对应JS使用代码:
<button onclick="postMessage()">JS调用Native</button>
//必须通过 xxx.postMessage才能将JS的数据发送到Native中,Native在回调中获取JS传递过来的数据
<script>
var bridge = window.webkit.messageHandlers.bridge;
function postMessage(){
alert('要发送消息了');
var message = {
'method' : 'hello',
'param1' : 'param',
};
bridge.postMessage(message);
}
</script>