随着HTML5的兴起并大行其道,目前市面上大多数的App都已经采用了Native和HTML结合的方式,笔者也不例外,随着苹果随iOS8一起开放了Webkit框架,HTML和Navive code的交互也变得越发简单和易用。
目前在iOS上Native code和HTML进行交互在iOS8以前主要是采用UIWebView,iOS8以后开放的Webkit框架则提供了更多更方便的功能,本篇主要介绍UIWebView和HTML的交互,Webkit框架将在下一篇文章中详细介绍。
本文demo代码下载地址:https://github.com/gongjiehong/JSTestDemo
虽然UIWebView只提供了一个方法用于通过字符串的形式调用网页JS,但是我们可以充分利用这个方法,达到想要的效果。
HTML网页中本身包含了一些可调用的属性,例如获取网页标题
webView.stringByEvaluatingJavaScriptFromString(“document.title”)
获取网页的字符编码
webView.stringByEvaluatingJavaScriptFromString(“document.charset”)
刷新当前页面
webView.stringByEvaluatingJavaScriptFromString(“documeny.location.reload()“)
其他更多属性参考此文件:属性整理
调用网页中的js方法,需要在HTML文件或JS文件中定义好标签和方法,然后通过跟上面一样的方法进行调用。
HTML代码如下,此处引入了md5.js,网页加载完成后即可调用md5.js中的方法。另外也可以直接在HTML中通过script标签定义js方法。
<script src=“js/md5.js”></script>
<script Type=‘text/javascript’>
function sendToken(token) {
alert(token);
/*这里拿到信息以后就可以到后台做一些肮脏的PY交易了*/
return md5(token);
}
</script>
调用方法如下(完整代码见Demo):
首先调用md5.js文件中的md5方法,得到参数的md5值,然后通过调用HTML文件中的sendToken方法将参数传递给网页
let md5FromJs = testWebView.stringByEvaluatingJavaScriptFromString(“md5(\(paramString!))”)
if let result = testWebView.stringByEvaluatingJavaScriptFromString(“sendToken(‘\(md5FromJs!)’)”) {
print(result.uppercaseString)
}
js注入的原理实际上是把我们本地组装好的js代码或者本地文件中存储的js代码注入到UIWebView中,然后就可以像上面一样调用js方法了。
demo.js中定义了一个方法,将网页跳转到淘宝网,我们把这个文件注入UIWebView中后,就可以调用这个方法实现跳转了,当然这个js文件中定义任意你想要执行的操作。
demo.js 代码:
function change_url_and_load(url) {
alert(url)
window.location.href = url
}
注入和调用代码:
let fileManager = NSFileManager.defaultManager()
let documentsPath = NSString(string: fileManager.documentsPath)
let webDirDocumentsPath = documentsPath.stringByAppendingPathComponent(“HTML”)
let demoJsPath = webDirDocumentsPath.stringByAppendingString(“/js/demo.js”)
do {
let jsString = try String(contentsOfFile: demoJsPath)
// 注入
testWebView.stringByEvaluatingJavaScriptFromString(jsString)
// 调用我们在js中定义的方法
let tempstr = testWebView.stringByEvaluatingJavaScriptFromString(“change_url_and_load(‘https://www.taobao.com/’)”)
println(tempstr)
}
catch {
}
HTML调用Native Code主要有两种方式,第一种是页面内herf跳转会触发UIWebView的回调,在回调中判断链接是否加入了我们自定地的标记,如果有,做我们自己的操作,没有则正常跳转。第二种是通过拦截js alert(私有Api),如果alert的内容中有我们自定义的标记,则进行我们自己的操作并决定是否要弹出提示框,没有则正常弹出。
定义HTML跳转链接,以下我们定义标记’forclient’为传参标记,textParam代表后面包含的是文本参数,jsonParam代表后面跟的是一段json,可以携带大量参数。
HTML代码:
<ul class=“navigation”>
<li><a href=“https://www.baidu.com“>这里是一个正常跳转链接</a></li>
<li><a href=“https://forclient?textParam=123456“>这里是一个带有特殊参数的跳转</a></li>
<li><a href=“https://forclient?jsonParam=%7B%22user_id%22%3A%20%2210086%22%2C%20%22user_name%22%3A%20%22%E7%A8%8B%E5%BA%8F%E7%8C%BF%E8%80%81%E9%BE%9A%22%2C%20%22action%22%3A%20%22show_info%22%2C%20%22gender%22%3A%20%221%22%2C%20%22age%22%3A%20%2225%22%7D“>这里是一个带有json参数的跳转</a></li>
<li><a href=“https://forclient?jsonParam=%7B%22user_id%22%3A%20%2210086%22%2C%20%22user_name%22%3A%20%22%E7%A8%8B%E5%BA%8F%E7%8C%BF%E8%80%81%E9%BE%9A%22%2C%20%22action%22%3A%20%22get_token%22%2C%20%22gender%22%3A%20%221%22%2C%20%22age%22%3A%20%2225%22%7D“>这里还是一个带有json参数的跳转</a></li>
</ul>
回调拦截代码,这里主要是查找我们定义好的特殊标记,然后通过解析json获取到参数然后进行相应的操作,详细操作代码见demo:
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if let urlString = request.URL?.absoluteString {
if urlString.rangeOfString(“forclient/”) != nil {
if let textParamRange = urlString.rangeOfString(“?textParam=”) {
handleTextParam(urlString.substringFromIndex(textParamRange.endIndex))
}
else if let jsonParamRange = urlString.rangeOfString(“?jsonParam=”) {
handleJsonStringParam(urlString.substringFromIndex(jsonParamRange.endIndex))
}
else {
// 其他操作方式穷举
}
return false
}
return true
}
return false
}
拦截js alert 需要添加如下方法和对应的实现,主要原理是通过拦截js alert 的 message内容,判断是否有我们自定义的标记并进行相应处理。同时还可以去除js alert 不美观的标题并替换成我们自己的标题。用swift编写时需要加入dynamic来标记方法,否则swift自动进行静态优化后runtime无法找到我们实现的方法,会造成崩溃或程序假死。
普通只有提示的JS Alert拦截和实现,这里我们就可以跟上面拦截地址一样,判断message是否有我们自己的标识并处理了,这里就不列举实际例子了:
– (void)webView:(UIWebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(id)frame;
– (void)webView:(UIWebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(id)frame {
UIAlertView* customAlert = [[UIAlertView alloc] initWithTitle:nil
message:message
delegate:nil
cancelButtonTitle:@”OK”
otherButtonTitles:nil];
[customAlert show];
}
拦截带操作和输入框的JS Alert
– (BOOL)webView:(UIWebView *)sender runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(id)frame;
– (NSString *)webView:(UIWebView *)view runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)text initiatedByFrame:(id)frame;
– (NSString *) webView:(UIWebView *)view runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)text initiatedByFrame:(id)frame {
return @””;
}
– (BOOL)webView:(UIWebView *)sender runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(id)frame {
UIAlertView *confirmDiag = [[UIAlertView alloc] initWithTitle:nil
message:message
delegate:self
cancelButtonTitle:@”取消”
otherButtonTitles:@”确定“, nil];
[confirmDiag show];
CGFloat version = [[[UIDevice currentDevice] systemVersion] floatValue];
if (version >= 7.) {
while (isEnd == NO) {
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01f]];
}
}else
{
while (isEnd == NO && confirmDiag.superview != nil) {
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01f]];
}
}
isEnd = NO;
return status;
}
– (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
status = buttonIndex;
isEnd = YES;
}
UIWebView上Native Code调用网页操作主要是通过字符串的方式调用js方法和属性并传递参数。网页操作原生代码主要通过地址拦截然后传递参数来进行。通过这两条通道和约定好的规则即可完成客户端和网页的相互操作,交互问题就迎刃而解了。js alert 拦截的原理类似,但毕竟使用了私有api,要上架还是有风险,不推荐使用。