JS与OC交互
1、UIWebView
JS 与 OC 交互
UIWebView
在 iOS12 中已经被废弃,同时WKWebView
在iOS8中已经出现。所以无特殊情况的话,我们一般应该也是用不到前者了!
UIWebView
相比于 WKWebView
的使用上会简单很多。
UIWebView
初始化:
-(void)loadUIWebViewWithName:(NSString *)name{
self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
self.webView.delegate = self;
[self.view addSubview:self.webView];
// 加载本地 H5 文件
NSURL *url = [[NSBundle mainBundle] URLForResource:name withExtension:nil];
NSURLRequest *reqeust = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:reqeust];
}
UIWebViewDelegate
协议的几个方法:
// 拦截JS页面操作请求,JS 调用OC 方法时会用到
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
// 开始加载页面
- (void)webViewDidStartLoad:(UIWebView *)webView;
// 页面加载完毕
- (void)webViewDidFinishLoad:(UIWebView *)webView;
// 页面加载失败
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
1.1 OC 调用 JS 函数
OC 调用 JS 函数,有两种方法:
①使用stringByEvaluatingJavaScriptFromString,拼接 JS 字符串调用。
②使用 JSContext 上下文调用 JS 函数
第一种方法使用相对简单,但复杂业务可能无法实现。
1.1.1 OC 拼接 JS 字符串调用 JS 方法
JS 代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OC与JS交互</title>
<script>
function showAlert_hasReturnValue(str){
alert('OC 调用JS ' + str);
return '返回值给OC';
}
function showAlert_noReturnValue(str){
alert('OC 调用JS ' + str);
}
</script>
</head>
</html>
OC 中调用 JS 函数,是将 JS 拼接成字符串,作为 stringByEvaluatingJavaScriptFromString
方法的参数实现的。
// 无返回值
- (void)didClickLeftItem {
[self.webView stringByEvaluatingJavaScriptFromString:@"showAlert_noReturnValue('无返回值')"];
}
// 有返回值
- (void)didClickRightItem {
NSString *resString = [self.webView stringByEvaluatingJavaScriptFromString:@"showAlert_hasReturnValue('有返回值')"];
NSLog(@"%@", resString);
}
1.1.2 使用 JSContext 上下文环境调用 JS 函数
UIWebView 加载完成时,初始化上下文环境:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
self.context = context;
}
// 无返回值
- (void)didClickLeftItem3 {
NSDictionary *dict = @{@"name": @"gorpeln", @"age": @28};
// 上下文调用 JS 函数
[self.context[@"ocCallJS_byJSContext"] callWithArguments:@[dict]];
}
1.2 JS 调用 OC 函数
JS 调用 OC 函数有三种实现方式:
① webView: shouldStartLoadWithRequest: 协议中拦截 JS 操作。
② 使用 JavaScriptCore,向 JS 中注册 OC 类,JS 函数中直接调用 OC 方法。
③ 使用 JSContext 上下文,JS 回调 OC 代码块。
1.2.1 OC 拦截 JS 超链接操作请求
HTML 文件中,需要调用 OC 方法的标签,添加超链接属性(超链接协议可自定义)。那么该标签的操作将在 UIWebViewDelegate
协议方法 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 中被监听拦截。
JS 文件如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OC与JS交互</title>
<script>
</script>
</head>
<body>
<div>
<a href="gorpeln://jsCallOC:/helloword/js">JS 调用 OC 方法(该标签的操作将被 OC 拦截)
</a>
</div>
</body>
</html>
当点击<a>标签时,在 下方协议方法中我们将从request中获取URL信息:
#pragma mark - UIWebViewDelegate
// 加载所有请求数据,以及控制是否加载
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
// URL ==> gorpeln://jsCallOC:/helloword/js
NSURL *requestURL = request.URL;
// URL 的一般格式为 scheme://host:port/path?
NSString *scheme = requestURL.scheme;
NSString *host = requestURL.host;
NSNumber *port = requestURL.port;
NSArray *paths = requestURL.pathComponents;
// URL 全路径字符串
NSString *absoluteString = requestURL.absoluteString;
// 端口后的相对路径
NSString *path = requestURL.path;
return YES;
}
根据以上从 URL 中获取的数据,可以在此设置路由调用 OC 中指定的方法。前提当然是自己协商一致 URL 数据格式。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
// request : host + 路由 : 拦截
if ([request.URL.scheme isEqualToString:@"gorpeln"]) {
// 方法名 gorpeln://jsCallOC:/helloword/js
NSString *routerName = request.URL.host;
SEL methodSEL = NSSelectorFromString(routerName);
// 测试方法为 jsCallOC
NSLog(@"routerName => %@", routerName);
if ([self respondsToSelector:methodSEL]) {
objc_msgSend(self,methodSEL,@"");
} else {
NSLog(@"没有找到对应的路由方法");
}
return NO;
}
return YES;
}
/** JS 调用 OC 的响应方法 */
- (void)jsCallOC {
NSLog(@"被JS调用的方法!");
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"提示" message:@"JS调用OC方法" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[controller addAction:okAction];
[self presentViewController:controller animated:YES completion:nil];
}
1.2.2 向 JS 中注入 OC 类
向 JS 注入 OC 类,需要引入 <JavaScriptCore/JavaScriptCore.h>
框架,使用 JSContext
向 JS 中注入 OC 类。
同时,被 JS 调用的 OC 方法,需要遵守 JSExport
协议。
例如,需求为 JS 调用 OC 的 getUserInfo 方法,获取应用内用户信息。
首先,要将该方法使用协议约定,并继承JSExport
协议:
@protocol TestProtocol <JSExport>
- (NSString *)getUserInfo;
@end
然后在注入JS的类中遵守TestProtocol协议并实现 getUserInfo 方法 ,demo中直接在控制器实现:
@interface UIWebViewDemo ()<UIWebViewDelegate,TestProtocol>
@property (nonatomic, strong) UIWebView *webView; //
@property (nonatomic, strong) JSContext *context; //
@end
当 UIWebView
加载完成的时候,使用JSContext
向 WebView 中注入OC类:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 可以注入实例对象也可以注入类对象
context[@"ViewController"] = self;
}
- (NSString *)getUserInfo{
NSLog(@"JS调用OC方法");
return @"name = gorpeln";
}
相应的在JS 中,可以直接使用 OC 类和方法调用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OC与JS交互</title>
<script>
function getUserInfo() {
// 注入OC类以后,此处通过OC类名可直接调用
var val = ViewController.getUserInfo();
alert(val);
}
</script>
</head>
<body>
<input type="button" value="注入OC类的方式 调用OC方法,并获取返回值" onClick="getUserInfo()" />
</body>
</html>
1.2.3 使用 JSContext 上下文,JS函数 回调 OC 代码块
JS 代码中blockOCCode()就是执行的OC代码块,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OC与JS交互</title>
<script>
function jsCallOC_byJSContext() {
// arr 是 OC 注入的全局属性,可以在此处处理后,传递给OC
// 对注入的属性,做业务处理后返回给 OC
arr.splice(0, 1)
blockOCCode(arr)
}
</script>
</head>
<body>
<input type="button" value="JS 调用 OC 方法(JS 回调 OCBlock 代码块)" onClick="jsCallOC_byJSContext()" />
</body>
</html>
同理,在OC代码中,当页面加载完毕,就要拿到上下文的引用,注入属性及blockOCCode 代码块赋值:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
// 拿到 JS 上下文引用
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
self.context = context;
self.context[@"ViewController"] = self;
// js 中注入全局变量
[context evaluateScript:@"var arr = ['张三', '李四', '王五', '赵六']"];
context[@"blockOCCode"] = ^(NSArray *jsArr){
// jsArr 是 JS 传递给 OC代码块的参数
NSLog(@"blockOCCode->jsArr == %@", jsArr);
// 通过上下文拿到 JS 全局属性
NSArray *orgArr = [JSContext currentArguments];
NSLog(@"blockOCCode->orgArr == %@", orgArr);
};
}
如此即可实现 JS 回调 OC 代码块。
1.3 异常收集
JS 异常收集代码:
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
// 异常收集
self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"%@",exception);
};
}
2、WKWebView
OC 与 JS 交互
WKWebView 初始化:
-(void)loadWKWebViewWithName:(NSString *)name{
// 配置类
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
// 适配移动设备
NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
WKUserContentController *wkUController = [[WKUserContentController alloc] init];
[wkUController addUserScript:wkUScript];
configuration.userContentController = wkUController;
// 初始化 WebView
self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
// <WKUIDelegate, WKNavigationDelegate>
self.webView.UIDelegate = self;
self.webView.navigationDelegate = self;
[self.view addSubview:self.webView];
// 加载(本地) H5 文件
NSString *urlStr = [[NSBundle mainBundle] pathForResource:name ofType:nil];
NSURL *fileURL = [NSURL fileURLWithPath:urlStr];
[self.webView loadFileURL:fileURL allowingReadAccessToURL:fileURL];
}
WKUIDelegate
协议:常用该协议替换 JS 弹窗提示,以获得更好的用户体验。
// 创建一个新的 WebView 视图时调用
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;
// 视图关闭时调用
- (void)webViewDidClose:(WKWebView *)webView;
// JS 警告框调用
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
// JS 确认框调用
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
// JS prompt()函数,该方法会被执行
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler;
2.1 OC 调用 JS 函数
OC 调用 JS 是通过 evaluateJavaScript: completionHandler:
方法实现的,JS 代码比较简单:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OC与JS交互</title>
<script>
function ocCallJS(str) {
alert("OC ---> JS" + str);
<!-- return value 通过 evaluateJavaScript: completionHandler: 回调中的值传递给OC -->
return "ocCallJS return value";
}
</script>
</head>
</html>
OC 代码也不难:
/** OC 调用 JS 返回值在 completionHandler 的回调参数 result 里 */
- (void)didClickLeftItem{
// OC --> JS 有返回值
NSString *jsStr = @"ocCallJS('WK_ocCallJS:OC-->JS')";
[self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
// result 是 JS return 回来的值
NSLog(@"%@----%@",result, error);
}];
}
#pragma mark - WKWebView默认禁止了下 Alert弹窗
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
UIAlertController * alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message ? : @"" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction * action = [UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}];
[alertController addAction:action];
[self presentViewController:alertController animated:YES completion:nil];
}
2.2 JS 调用 OC
WKWebView 中 JS 调用 OC 函数有两种实现方式:
① 使用 WKNavigationDelegate 协议 webView: decidePolicyForNavigationAction: decisionHandler: 方法拦截 JS 操作请求。
② 使用 WKScriptMessageHandler 协议 userContentController: didReceiveScriptMessage: 方法,接收 JS 发来的特定消息进行解析处理。
2.2.1 拦截 JS 超链接请求
HTML 文件中,需要调用 OC 方法的标签,添加超链接属性(超链接协议可自定义)。那么该标签的操作将在 WKNavigationDelegate
协议方法 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 中被监听拦截。
JS 代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OC与JS交互</title>
</head>
<body>
<div>
<a href="gorpeln://WKWebView_jsCallOC:/helloword/js"> WK 中调用OC</a>
<div/>
<br />
</body>
</html>
OC 中拦截,并使用路由调用指定 OC 方法:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if ([navigationAction.request.URL.scheme isEqualToString:@"gorpeln"]) {
NSLog(@"-------------");
NSLog(@"requestURL= %@",navigationAction.request.URL);
NSLog(@"scheme= %@",navigationAction.request.URL.scheme);
NSLog(@"host= %@",navigationAction.request.URL.host);
NSLog(@"port= %@",navigationAction.request.URL.port);
NSLog(@"absoluteString= %@",navigationAction.request.URL.absoluteString);
NSLog(@"path= %@",navigationAction.request.URL.path);
NSLog(@"query= %@",navigationAction.request.URL.query);
NSLog(@"-------------");
NSString *routerName = navigationAction.request.URL.host;
SEL methodSEL = NSSelectorFromString(routerName);
NSLog(@"routerName => %@", routerName);
if ([self respondsToSelector:methodSEL]) {
objc_msgSend(self,methodSEL,@"");
} else {
NSLog(@"没有相应路由");
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
// 被 JS 调用的 OC 方法
- (void)WKWebView_jsCallOC {
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"提示" message:@"WK 中JS调用OC方法" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[controller addAction:okAction];
[self presentViewController:controller animated:YES completion:nil];
}
2.2.2 OC 接收 JS 发来的消息
该方法使用步骤上相对繁琐,但 WKScriptMessageHandler 协议中只有一个方法 userContentController: didReceiveScriptMessage:
① 引入 WKScriptMessageHandler
协议,并使用 config 初始化 WKWebView。
② 注册 ScriptMessageHandler
。
③ 实现协议方法。
④ 移除 ScriptMessageHandler
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OC与JS交互</title>
<script>
function jsSendMessage(){
// js 发送消息, messgaeToOC 是在 OC 代码中注册过的名称
var val = window.webkit.messageHandlers.messgaeToOC.postMessage("js 发送给 OC 的消息");
alert(val);
}
</script>
</head>
<body>
<div>
<input type="button" value="messgaeHandle" onClick="jsSendMessage()" />
<div/>
<br />
</body>
</html>
OC 代码实现步骤:
// ① 引入协议 <WKScriptMessageHandler
// ② 注册消息处理名称为:messgaeToOC
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"messgaeToOC"];
}
// ③ 实现协议方法
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
// message.name 就是我们注册的 messgaeToOC
// message.body 就是JS发送过来的消息
NSLog(@"%@---%@",message.name, message.body);
// 根据这两个参数 写我们的业务代码
}
// ④ 控制器销毁时移除 `ScriptMessageHandler`
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"messgaeToOC"];
}
3、第三方库实现 OC 与 JS 交互
3.1 WebViewJavascriptBridge
库使用
使用步骤:
① 引入头文件
② 初始化桥接类实例
③ (JS 调用 OC)OC中 registerHandler 注册JS方法,使用block传参及回调;( OC 调用 JS)callHandler 调用 JS 方法,使用block回调JS返回结果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OC与JS交互</title>
<script>
<!-- 使用该库,JS 中代码中需要添加:-->
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)
}
<!--处理 oc 调用 js -->
setupWebViewJavascriptBridge(function(bridge) {
bridge.registerHandler('OCCallJSFunction', function(data, responseCallback) {
alert('JS方法被调用:' + data);
responseCallback({'userId': '123456'});
})
})
<!--处理 js 调用 oc -->
function jsCallsOCFunc(){
WebViewJavascriptBridge.callHandler('jsCallsOC', {'name': 'gorpeln'}, function(response) {
alert(response);
})
}
</script>
</head>
<body>
<div>
<input type="button" value="JS 调用 OC 方法" onclick="jsCallsOCFunc()" /> <br />
<div/>
<br />
<br />
</body>
</html>
OC 代码实现:
- (void)createWjb {
// 初始化桥接类实例
self.wjb = [WebViewJavascriptBridge bridgeForWebView:self.webView];
// 设置 WKNavigationDelegate 代理,保留该协议使用者依然可用
[self.wjb setWebViewDelegate:self];
}
/*
含义:JS调用OC
@param registerHandler 要注册的事件名称(比如这里我们为jsCallsOC)
@param handel 回调block函数 当后台触发这个事件的时候会执行block里面的代码
*/
/**
// JS 单纯的调用 OC 的 block
WebViewJavascriptBridge.callHandler('jsCallsOC');
// JS 调用 OC 的 block,并传递 JS 参数
WebViewJavascriptBridge.callHandler('jsCallsOC',"JS 参数");
// JS 调用 OC 的 block,传递 JS 参数,并接受 OC 的返回值。
WebViewJavascriptBridge.callHandler('jsCallsOC',{data : "这是 JS 传递到 OC 的扫描数据"},function(dataFromOC){
alert("JS 调用了 OC 的扫描方法!");
document.getElementById("returnValue").value = dataFromOC;
});
*/
- (void)jsCallOC {
// JS-->OC
[self.wjb registerHandler:@"jsCallsOC" handler:^(id data, WVJBResponseCallback responseCallback) {
// data 是 JS 传递给OC 的参数,responseCallback可将执行结果回调给 JS
NSLog(@"%@---%@----%@",[NSThread currentThread],data,responseCallback);
responseCallback(@"JS调用的OC方法已执行");
}];
}
/*
含义:OC调用JS
@param callHandler 商定的事件名称,用来调用网页里面相应的事件实现
@param data id类型,相当于我们函数中的参数,向网页传递函数执行需要的参数
注意,这里callHandler分3种,根据需不需要传参数和需不需要后台返回执行结果来决定用哪个
*/
/**
// 单纯的调用 JSFunction,不往 JS 传递参数,也不需要 JSFunction 的返回值。
[_jsBridge callHandler:@"OCCallJSFunction"];
// 调用 JSFunction,并向 JS 传递参数,但不需要 JSFunciton 的返回值。
[_jsBridge callHandler:@"OCCallJSFunction" data:@"把 HTML 的背景颜色改成橙色!!!!"];
// 调用 JSFunction ,并向 JS 传递参数,也需要 JSFunction 的返回值。
[_jsBridge callHandler:@"OCCallJSFunction" data:@"传递给 JS 的参数" responseCallback:^(id responseData) {
NSLog(@"JS 的返回值: %@",responseData);
}];
*/
- (void)OCCallJS {
[self.wjb callHandler:@"OCCallJSFunction" data:@"传递参数param" responseCallback:^(id responseData) {
// data 是 OC 传递给 JS 的参数,responseData是 JS 执行完成后回调给OC 的执行结果
NSLog(@"%@--%@",[NSThread currentThread],responseData);
}];
}