UIWebView中Objective-C与JavaScript交互
现在越来越多的项目为了支持公司业务的发展而选择使用HTML5做一些功能,那么这样就会涉及到HTML5与原生Objective-C的交互,今天就聊聊HMTL与原生之间的相互调用问题。
概述
原生与Web页面的交互可以分为Objective-C执行JavaScript代码和Web页面(或JavaScript)执行Objective-C代码,前者相对非常简单,后面可以通过JavaScriptCore、拦截协议等方式实现,下面我将主要对这二种方式进行叙述。
Objective-C执行JavaScript代码
打开UIWebView的头文件我们可以发现有一个方法叫stringByEvaluatingJavaScriptFromString:,它就是Objective-C执行JavaScript代码的通路,并且要注意它的返回值是NSString,这个方法在平时非常实用,列举几个例子:
// 获取当前页面的title
NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"];
// 获取当前页面的可高度
float offsetHeight = [[webview stringByEvaluatingJavaScriptFromString:@"document.body.offsetHeight"] floatValue];
// 获取需要分享的类内
NSString *sharedDictionary = [webview stringByEvaluatingJavaScriptFromString:@"getSharedContent"];
JavaScript执行Objective-C代码
JavaScript执行Objective-C代码之前我就说过有二种方式,现在我分别来说说这二种方式。
1. 利用JavaScriptCore来执行Objective-C代码
iOS7之后苹果推出了JavaScriptCore这个框架,从而让Web页面和本地原生应用交互起来非常方便,而且使用此框架可以做到Android那边和iOS相对统一,web前端写一套代码就可以适配客户端的两个平台,从而减少了Web前端的工作量。
Web前端
在三端交互中,web前端要强势一些,一切传值、方法命名都按web前端开发人员来定义,让另外两端去做适配。在这里以调用分享为例来详细讲解,测试网页代码取名为shared.html,其代码内容如下:
shared.html代码内容
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>js调用objective-c代码</title>
<style type="text/css">
#backButton {
display: inline-block;
width:50px; height:30px;
}
</style>
</head>
<body>
<button id="backButton">返回</button>
<script type="text/javascript">
var btn = document.getElementById('backButton');
btn.addEventListener('click', function() {
callBackObj.letsGoBack();
});
</script>
</body>
</html>
iOS
iOS这边根据前端定义的方法名来写代码,但是有些时候web前端会让我们定义,但是我们定义好之后他又要修改,这时候就会很烦啊。所以碰到三端交互的时候最好就是让web前端去定义方法名,iOS和Android根据web前端定义好的去写代码。JavaScriptCore中web页面调用原生应用的方法可以用Delegate或Block两种方法,此文以按Delegate讲解。
JavaScriptCore中类及协议:
JSContext:给JavaScript提供运行的上下文环境
JSValue:JavaScript和Objective-C数据和方法的桥梁
JSManagedValue:管理数据和方法的类
JSVirtualMachine:处理线程相关,使用较少
JSExport:这是一个协议,如果采用协议的方法交互,自己定义的协议必须遵守此协议
ViewController中的代码
- (void)webViewDidFinishLoad:(UIWebView *)webView {
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
[context setExceptionHandler:^(JSContext *ctx, JSValue *value) {
NSLog(@"error: %@", value);
}];
context[@"callBackObj"] = self;
}
- (void) letsGoBack {
[self.navigationController popViewControllerAnimated:YES];
}
ViewController中的代码解释
看到上面的代码我们可以看到设置了context中的callBackObj为self,通过这样的设置js在使用callBackObj.letsGoBack时其实等价于在viewController中使用[self letsGoBack]。
运行效果如图所示
拦截协议
首先我说解释一下什么是协议,协议是共同计议的规定,是大家一起遵循的准则。拦截协议这个非常适合各个平台,不需要引入什么框架,只需要大家相互之间遵守协议就可以了。那么在我们团队中协议是怎么定义的呢?scheme://model/aciton?{参数1}={数值1}&{参数2}={数值2}&…,比如在金蛋中分享的协议会是:jindanlicai://active/shared?callback=result(‘%s’,‘%s’)&function=getContent()。其中,function的值是可以获取分享内容的JavaScript函数名称,callback的值是分享结束之后调用的方法来告知HTML5是否分享成功与失败状态。
web前端
home.html中的代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div>
<input type="button" value="分享" onclick="share()">
</div>
<script>
function share() {
window.location.href = 'jindanlicai://active/shared?callback=result('%s','%s')&function=getContent()';
}
</script>
</body>
</html>
home.html中的代码解释
通过点击分享按钮将会调用JavaStript中的share(),window.location.href这里是改变主窗口的指向从而马上发出一个链接为‘jindanlicai://active/shared?callback=result(‘%s’,‘%s’)&function=getContent()’请求,而在iOS方法中我们要拦截这个请求,根据URL请求内容去判断Web页面想要原生做的事情,从而实现web页面和本地应用之间的交互。
iOS
iOS对应的代码
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSString *url = request.URL.absoluteString;
if ([[request.URL scheme] isEqualToString:@"jindanlicai"]) {
[JDOpenURL handlerURL:request.URL context:self];
return NO;
}
return YES;
}
iOS对应的代码的解释
在webView的代理方法中去拦截URL的scheme为自定义jindanlicai协议,调用原生提前约定好的方法,并且返回值为NO来阻止此链接的跳转。
总结
随着手机硬件的配置越来越强大和HTML5的兴起,一个App完全可以由web页面来写。现在在我们公司已经有很多的App已经完全是这么干了,iOS和Android只是给这个App套一个壳并通过一些协议去实现一些本地逻辑。协议是一个非常不错的方法,在这我推荐使用协议去做这样的事,当前提前是你要非常理解协议。如果有人喜欢协议,WebViewJavaScriptBridge是一个不错的第三库。