iOS WebView 与JS的交互
最近项目中需要使用JS与原生的交互,发现了不少的问题,做一个总结
UIWebView
- JS调用OC无参
- JS调用OC有一个参数
- JS调用OC有多个参数
- OC调用JS并传值
1.创建UIWebView
- (UIWebView *)webView {
if (!_webView) {
_webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
_webView.delegate = self;
NSURL *localHTMLURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"UIWebViewJSInteraction" ofType:@"html"]];
NSURLRequest *request = [NSURLRequest requestWithURL:localHTMLURL];
[_webView loadRequest:request];
}
return _webView;
}
2.通过KVO
获取JS上下文javaScriptContex
@weakify(self)
[[self.webView rac_valuesForKeyPath:@"documentView.webView.mainFrame.javaScriptContext" observer:self] subscribeNext:^(id x) {
@strongify(self)
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
YYWeakProxy *proxy = [YYWeakProxy proxyWithTarget:self];
[context setObject:proxy forKeyedSubscript:@"liqu_app"];
self.context = context;
}];
注: A JSContext object represents a JavaScript execution environment. You create and use JavaScript contexts to evaluate JavaScript scripts from Objective-C or Swift code, to access values defined in or calculated in JavaScript, and to make native objects, methods, or functions accessible to JavaScript.
译:大致的意思就是,通过这个对象我们可以访问JavaScript中的变量以及函数方法
- JS调用OC方法,是通过将对象注入到
JSContext
中,这个对象必须实现JSExport
协议,这个协议定义了这个对象在JS环境中能够调用的方法
@protocol UIWebViewJSExport <JSExport>
// 注意:此处一定不要写@optional,否则js不会回调协议里面的方法
//@optional
- (void)jsInvokeOCNoParm;
- (void)jsInvokeOCWithParm1:(NSString *)parm1;
- (void)jsInvokeOCWithParm1:(NSString *)parm1 parm2:(NSString *)parm2;
- (NSString *)jsInvokeOCReturnValue;
@end
4 . JS
与OC
相互交互代码
// html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS调用OC方法</title>
</head>
<body>
<input type="button" value="JS调用OC方法无参数" onclick="jsInvokeOCNoParm()" disable='true'/>
<input type="button" value="JS调用OC方法有一个参数" onclick="jsInvokeOCWithParm1();"/>
<input type="button" value="JS调用OC方法有两个参数" onclick="jsInvokeOCWithParm1Parm2();"/>
<input type="button" value="OC调用JS方法有返回值" onclick="jsInvokeOCReturnValue();"/>
</body>
<script>
function jsInvokeOCNoParm(){
window.liqu_app.jsInvokeOCNoParm();
}
function jsInvokeOCWithParm1(){
window.liqu_app.jsInvokeOCWithParm1('this is a parm');
}
function jsInvokeOCWithParm1Parm2(){
var model = new Object();
model.name = 'js';
model.age = 18;
window.liqu_app.jsInvokeOCWithParm1Parm2('this is a parm',JSON.stringify(model));
}
function jsInvokeOCReturnValue(){
var str = window.liqu_app.jsInvokeOCReturnValue();
var model = JSON.parse(str);
alert('jsInvokeOCReturnValue___' + model.name);
}
<!--模拟打开App立即获取App内部信息的过程-->
jsInvokeOCReturnValue();
function ocCallJSMethod(str){
var model = JSON.parse(str);
alert('ocCallJSMethod_____' + model.name);
return true;
}
</script>
<style>
input {
display:block;
margin:20px auto;
}
</style>
</html>
- JS调用OC的函数
// oc
#pragma mark -UIWebViewJSExport
- (void)jsInvokeOCNoParm {
NSLog(@"%@",NSStringFromSelector(_cmd));
}
- (void)jsInvokeOCWithParm1:(NSString *)parm1 {
NSLog(@"%@,parm:%@",NSStringFromSelector(_cmd),parm1);
}
- (void)jsInvokeOCWithParm1:(NSString *)parm1 parm2:(NSString *)objStr {
//objStr 从html传出对象到oc,这事一个字符串,需要我们转成oc对象
NSData *data = [objStr dataUsingEncoding:NSUTF8StringEncoding];
id obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSLog(@"%@,parm1:%@ parm2:%@",NSStringFromSelector(_cmd),parm1,obj);
}
// 从本地获取信息到HTML
- (NSString *)jsInvokeOCReturnValue {
UserInfo *info = [UserInfo new];
info.name = @"liqu";
info.age = 18;
// 对象转字符串(实现Android和iOS逻辑统一)
NSString *json = [info mj_JSONString];
return json;
}
- OC调用JS的函数
#pragma mark -oc调用js方法
- (BOOL)ocCallJSMethod {
// 方式1:
UserInfo *info = [UserInfo new];
info.name = @"liqu";
info.age = 18;
JSValue *jsResult = [self.context evaluateScript:[NSString stringWithFormat:@"ocCallJSMethod('%@')",[info mj_JSONString]]];
BOOL result = [jsResult toBool];
NSLog(@"%d",result);
//方式2:
{
JSValue *ocInvokeJs = self.context[@"ocCallJSMethod"];
JSValue *jsResult = [ocInvokeJs callWithArguments:@[[info mj_JSONString]]];
BOOL result = [jsResult toBool];
NSLog(@"%d",result);
}
return result;
}
WKWebView
- 导入
#import <WebKit/WebKit.h>
- 向
JS
中注入模型对象 - 实现
WKWebView
代理,判定引起这个回调的模型对象,执行相应代码
- 创建
WKWebView
- (WKWebView *)webView {
if (!_webView) {
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.userContentController = [[WKUserContentController alloc]init];
YYWeakProxy <WKScriptMessageHandler>*proxy = (id<WKScriptMessageHandler>)[YYWeakProxy proxyWithTarget:self];
[configuration.userContentController addScriptMessageHandler:proxy name:@"liqu_app"];
_webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
_webView.UIDelegate = self;
NSURL *localHTMLURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"WKWebViewJSInteraction" ofType:@"html"]];
NSURLRequest *request = [NSURLRequest requestWithURL:localHTMLURL];
[_webView loadRequest:request];
}
return _webView;
}
注意:
[config.userContentController addScriptMessageHandler:'会回调这个对象里面的方法' name:@"为这个对象打上一个标签,以便代理中进行区分"];
这个方法中我们需要遵循WKScriptMessageHandler
这个协议,这个协议只定义了一个方法,这个方法会在JS
调用OC
时会被调用
2.HTML
代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS调用OC方法</title>
</head>
<body>
<input type="button" value="JS调用OC方法无参数,看日志" onclick="jsInvokeOCNoParm()" disable='true'/>
<input type="button" value="OC调用JS方法有返回值" onclick="jsInvokeOCReturnValue();"/>
</body>
<script>
function jsInvokeOCNoParm() {
window.webkit.messageHandlers.liqu_app.postMessage('123');
}
function jsInvokeOCReturnValue(){
window.webkit.messageHandlers.liqu_app.postMessage('fetchUserInfo');
}
<!--模拟打开App立即获取App内部信息的过程-->
window.webkit.messageHandlers.liqu_app.postMessage('fetchUserInfo');
function ocCallJSMethod(str){
var model = JSON.parse(str);
alert('ocCallJSMethod_____' + model.name);
return true;
}
</script>
<style>
input {
display:block;
margin:20px auto;
font-size:30px;
}
</style>
</html>
3.WKScriptMessageHandler
协议
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
// 根绝对应的name和body执行对应的逻辑
NSLog(@"%@ --- %@",message.body,message.name);
// deal
if ([message.body isEqualToString:@"fetchUserInfo"]) {
[self ocCallJSMethod];
}else {
}
}
4 .OC调用JS函数,使用如下方法
- (void)ocCallJSMethod {
UserInfo *info = [UserInfo new];
info.name = @"liqu";
info.age = 18;
NSString *jsMethod = [NSString stringWithFormat:@"ocCallJSMethod('%@')",[info mj_JSONString]];
[_webView evaluateJavaScript:jsMethod
completionHandler:^(id _Nullable obj, NSError * _Nullable
error)
{
NSLog(@"%@",obj);
}];
}
注意:这里我们传值给JS上下文,都用的
YYWeakProxy
对象,这个对象是self的代理,可以这么理解,因为无论UIWebView的
[context setObject:proxy forKeyedSubscript:@"liqu_app"];
或者WKWebView的
[configuration.userContentController addScriptMessageHandler:proxy name:@"liqu_app"];
都会对这个proxy进行强引用,会导致循环引用,虽然使用YYWeakProxy
能够避免循环引用,但是还是有内存泄漏,检测发现YYWeakProxy即使在控制器释放后,这个对象还是没有调用dealloc
方法,即使使用weak弱引用YYWeakProxy,这个对象也无法释放,如果你知道怎么解决,希望能告知!
最后附上代码
项目地址