关于UIWebView与JS的交互:
这里先声明一下:示例只放上了重点代码,后面会给demo地址。
1、原始交互方法:
1、OC调用JS:向UIWebView发送- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
消息来执行一段JavaScript脚本;这里需要注意的是:该方法必须在主线程调用,否则不起作用。
- 同时本人在项目时遇到这样的需求情况:原生controller进入下一级界面controller,该controller存在一个 webView,直接给webView传递参数,然后点击H5页面内按钮,webview进入次级界面,再次点击H5次级页面内按钮,进入下一级原生controller,而后返回有webView的controller界面并回传值给webView所在controller,当webView所在controller获取到返回数据后再传递给H5次级页面。
- 这里发生的问题就是,我在最后一步时回传值使用的是block,在block内部使用
dispatch_async(dispatch_get_main_queue(), ^{})
回归主线程后再调用stringByEvaluatingJavaScriptFromString:
向H5发送参数。但是此时并不能将参数发送成功,即stringByEvaluatingJavaScriptFromString:
不起作用。原因暂时不知,经排查确实是在主线程了。 - 解决方法:传值方式改为通知中心的方式,然后当收到通知后将参数发送。
- 这里发生的问题就是,我在最后一步时回传值使用的是block,在block内部使用
2、JS调用OC:在UIWebView的代理方法- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
中拦截URL然后重定向去执行OC相关代码;
示例:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
// 原始的JS调用OC,拦截URL,重定向
// 这里我在 index.html中采用了两种方式进行request返回,1:onclick="window.open('need://transform')";2:onclick="window.location.href='need://location'"
if ([request.URL.absoluteString hasPrefix:@"need://transform"]) {// 跳转
NSLog(@"执行了跳转操作");
return NO;
}
if ([request.URL.absoluteString hasPrefix:@"need://location"]) {// 本界面的一些操作
NSLog(@"执行了本界面操作");
return NO;
}
return YES;
}
// 这里是点击了OC中一个原生的button所执行的方法
- (void)rightButAction:(UIButton *)sender
{
// 使用UIWebView自带方法调用JS方法,其中picCallback('%@')是JS方法,后面是参数
NSString * jsStr = [NSString stringWithFormat:@"picCallback('%@')", @"stringByEvaluatingJavaScriptFromString方法实现"];
[self.mainWebView stringByEvaluatingJavaScriptFromString:jsStr];
}
2、使用JavaScriptCore:
关于JavaScriptCore框架可以参考这篇文章,当使用时需要先导入该框架头文件#import <JavaScriptCore/JavaScriptCore.h>
。
1、OC调用JS: 在代理方法- (void)webViewDidFinishLoad:(UIWebView *)webView
中获取交互上下文对象(JSContext
) ,然后调用JSContext
的- (JSValue *)evaluateScript:(NSString *)script;
方法执行JS代码;
2、JS调用OC:这里有两种方法,一种是针对JS中未指明调用对象的方法,一种是针对JS中指明调用对象的方法。
未指明调用对象的方法:可以直接通过context获取到该方法,赋予其block的回调方式即可;
指明调用对象的方法:需要创建继承JSExport的协议,协议方法要与JS中方法相同!通过context将某一类的实例赋予JS当做调用方法的对象,然后在该类中服从协议方法即可;
示例:
// JS调用OC
@protocol JSObjcDelegate <JSExport>
//协议的方法必须和JS里面的方法名称保持一致才有效!
- (void)callShare;
@end
@interface JRWebViewMutualViewController ()<UIWebViewDelegate, JSObjcDelegate>
@property (nonatomic,strong) JSContext * jsContext;// 获取交互环境,主要用于调取JS代码
@property(strong,nonatomic)UIWebView * mainWebView;
@end
@implementation JRWebViewMutualViewController
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
self.jsContext = [self.mainWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 未指明调用对象的方法
self.jsContext[@"callCamera"] = ^() {
NSLog(@"调用Camera了🙄");
};
/**
在JS中 onclick="callCamera()" 指的是点击button直接触发callCamera方法;
onclick="TEXT.callShare() 指点击button会让一个叫做TEXT的对象去触发callShare方法;
*/
//在使用JSExport协议类时必须有指定的执行对象才能使用否则使用block形式的回调即可
self.jsContext[@"TEXT"] = self;
// 若发生异常会执行此方法
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"异常信息是%@",exception);
};
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(@"加载错误:%@", error);
}
// 这里是点击了OC中一个原生的button所执行的方法
- (void)rightButAction:(UIButton *)sender
{
// 获取 将字符串对应的JS方法,转换成一个JSValue对象
JSValue * jsValue = [self.jsContext evaluateScript:@"picCallback"];
// 下面👇这一方法与上面的等效
// JSValue * jsValue = self.jsContext[@"picCallback"];
// 作为一个函数调用JSValue 参数是JS函数所需参数,该方法用于传参
[jsValue callWithArguments:@[@"javaScript实现"]];
// 与上面两句代码等效代码
// [self.jsContext evaluateScript:[NSString stringWithFormat:@"picCallback('%@')", @"javaScript实现"]];
}
- (void)callShare
{
NSLog(@"调用Share了🙄");
}
@end
在这里因为若没有HTML的代码可能会不是那么清晰,附上HTML的代码,里面比较简单:
<!DOCTYPE html>
<html>
<head lang="zh-CN">
<meta charset="UTF-8">
<title>OC-JS交互</title>
</head>
<body>
<div style="margin-top: 30px">
<input type="button" value="调用OC原生代码示例 - 拦截协议,跳转界面" onclick="window.open('need://transform')" style = "width:300px;height:30px;border:0px;background-color:red;margin-left:10px" >
</div>
<div>
<input type="button" value="调用OC原生代码示例 - 拦截协议,本界面做操作" onclick="window.location.href='need://location'" style = "width:300px;height:30px;border-style:none; background-color:#FF9;margin-left:10px; margin-top:10px">
</div>
<div>
<p><1>和后端同事协定好协议,如need://transform表示跳转,need://location表示本界面的其他操作。 <br>
<2>实现UIWebView代理的shouldStartLoadWithRequest:navigationType:方法,在方法中对url进行拦截,如果是步骤 <1> 中定义好的协议则执行对应原生代码,返回NO进行url拦截,否则返回YES继续加载原url。</p>
<div>
<div style="margin-top: 10px">
<input type="button" value="调用OC原生代码示例 - JSCore,跳转界面" onclick="callCamera()" style = "width:300px;height:30px;border:0px;background-color:red;margin-left:10px" >
</div>
<div>
<input type="button" value="调用OC原生代码示例 - JSCore,本界面做操作" onclick="TEXT.callShare()" style = "width:300px;height:30px;border-style:none; background-color:#FF9;margin-left:10px; margin-top:10px">
</div>
<script>
var picCallback = function(photos) {
alert(photos);
}
</script>
</body>
</html>
demo 地址,demo是一个项目集合,暂时没什么东西,会后续往里面加入,交互界面在左侧抽屉中😆。