心辰·Dev

Objective-C 和 JavaScript 双向交互详解

本文分两部分,分别讲解使用 UIWebView 和 iOS8 引入的 WKWebView 进行 Web <==> Native 双向交互的方式。

UIWebView

Objective-C 调用 JavaScript 方法

UIWebView 的 API stringByEvaluatingJavaScriptFromString:是在 WebView 中运行 JS 代码的实例方法。在 Web 端用 JS 提供具体的方法实现,在 native 端就可以方便的调用。
JS 文件中:

1
2
3
function myJavascriptFunction(){
// 实现
}

OC 文件中:

1
[webview stringByEvaluatingJavaScriptFromString: @“myJavascriptFunction()”];

JavaScript 调用 Objective-C 方法

在 Web 端用 JS 捕获重定向到新的页面的动作,并立刻取消,这样就能触发并利用 UIWebViewDelegate 协议中的 shouldStartLoadWithRequest代理方法,实现调用 OC 代码的需求。但实操过程中往往会引发一些难以解决的 bug。

最佳实践

创建一个 IFrame 并且用setAttribute方法对它的 location 进行重新赋值,这样可以触发 UIWebView 中的shouldStartLoadWithRequest方法。成功触发后再移除掉 IFrame.
JS 文件中:

1
2
3
4
5
6
7
8
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", "js-frame:myObjectiveCFunction";
iframe.setAttribute("height", "1px");
iframe.setAttribute("width", "1px");
document.documentElement.appendChild(iframe);
// 移除
iframe.parentNode.removeChild(iframe);
iframe = null;

OC 文件中:

1
2
3
4
5
6
7
8
9
10
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL * url = [request URL];
if ([[url scheme] isEqualToString:@"js-frame"]) {
// 调用 myObjectiveCFuntion
...
return NO;
}

return YES;
}

三方库

可引入 WebViewJavascriptBridge 第三方库来完成需求。该第三方库具有更强大的通信和回调机制。微信等知名应用均引入该三方库,显示了其强大的稳定性。

WKWebView(iOS8)

苹果在 iOS8 引入新的 WKWebView, 提供了一些新的 API ,相应地 Native 端和 Web 端的通信方式增加了更多的方式。

Objective-C 调用 JavaScript 方法

和 UIWebView 相似,利用方法 evaluateJavaScript:completionHandler: 在 WebView 运行 JS 代码。不同的是,这个 API 提供了运行 JS 完成时的回调,可以处理运行成功或失败后的操作。在 WKWebView 加载的特定阶段例还可以执行加入 WKUserContentController 的 UserScript.
OC 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)viewDidLoad {
// 初始化 WebView 的配置
WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc] init];
// 将本地或网络的 UserScript 加入 userContentController.
WKUserContentController *userContentController =[[WKUserContentController alloc] init];
NSString *path = [[NSBundle mainBundle] pathForResource: @"scriptName" ofType:@"js"];
NSString *code = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
WKUserScript *script = [[WKUserScript alloc] initWithSource:code injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
[userContentController addUserScript:[self script:@"messenger"]];
configuration.userContentController = userContentController;
configuration.preferences.javaScriptEnabled = YES;
// 初始化 WebView
...
}

JavaScript 调用 Objective-C 方法

WKWebView 相关架构中定义了三个协议(protocols), 分别是 WKNavigationDelegate 、 WKUIDelegate 、 WKScriptMessageHandler, 其中 WKScriptMessageHandler 的作用就是处理 Web JS 发送到 Native 端的信息。
JS文件中:

1
2
3
4
5
function alertClick(message){
window.webkit.messageHandlers.alert.postMessage({body: message});
}

<button type="button" style="width:200;height:80;font-Size:30px" onclick="alertClick('123213123')">alert</button>

OC 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)viewDidLoad {   
// 初始化 WebView 的配置
WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc] init];
// 注册供 JS 调用的方法
WKUserContentController *userContentController =[[WKUserContentController alloc] init];
[userContentController addScriptMessageHandler:self name:@"alert"];
configuration.userContentController = userContentController;
configuration.preferences.javaScriptEnabled = YES;
// 初始化 WebView
...
}

-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
// message.name: js 发送的方法名称
if(message.name ==nil || [message.name isEqualToString:@""])
return;
// message body: js 传过来的值
if ([message.name isEqualToString:@"alert"]) {
NSString *body = [message.body objectForKey:@"body"];
[self showAlert:body];
}
}