iOS中用来加载网页,有两个控件UIWebView(iOS8之前),WKWebView(iOS8诞生)。
此篇博文暂不讨论由UIWebView转到WKWebView适配的那些坑(Cookie问题,请求拦截问题等等)。
只是记录一下webView与JS交互的方案调研。
UIWebView交互
- URL Scheme
拦截链接,从而获取链接里的信息,只能进行JS专递信息到OC。可以利用代理方法实现。
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
// NSLog(@"Scheme: %@", [url scheme]); //方案
// NSLog(@"Host: %@", [url host]); //主机
// NSLog(@"Port: %@", [url port]); //端口
// NSLog(@"Path: %@", [url path]); //路径
// NSLog(@"Relative path: %@", [url relativePath]); //相关路径
// NSLog(@"Path components as array: %@", [url pathComponents]); //路径的每个组成部分
// NSLog(@"Parameter string: %@", [url parameterString]); //参数
// NSLog(@"Query: %@", [url query]); //查询
// NSLog(@"Fragment: %@", [url fragment]); //分段
//当前的链接
NSURL* url = [request URL];
NSString * string = [url absoluteString];
//链接转为字典,获取我们想要的信息
NSDictionary* dic = [self dictionaryFromQuery:string usingEncoding:NSUTF8StringEncoding];
NSLog(@"%@",dic);
//return NO 不允许跳转链接
return YES;
}
链接转字典的工具方法
- (NSDictionary*)dictionaryFromQuery:(NSString*)query usingEncoding:(NSStringEncoding)encoding {
NSCharacterSet* delimiterSet = [NSCharacterSet characterSetWithCharactersInString:@"&;"];
NSMutableDictionary* pairs = [NSMutableDictionary dictionary];
NSScanner* scanner = [[NSScanner alloc] initWithString:query];
while (![scanner isAtEnd]) {
NSString* pairString = nil;
[scanner scanUpToCharactersFromSet:delimiterSet intoString:&pairString];
[scanner scanCharactersFromSet:delimiterSet intoString:NULL];
NSArray* kvPair = [pairString componentsSeparatedByString:@"="];
if (kvPair.count == 2) {
NSString* key = [[kvPair objectAtIndex:0]
stringByReplacingPercentEscapesUsingEncoding:encoding];
NSString* value = [[kvPair objectAtIndex:1]
stringByReplacingPercentEscapesUsingEncoding:encoding];
[pairs setObject:value forKey:key];
}
}
return [NSDictionary dictionaryWithDictionary:pairs];
}
- stringByEvaluatingJavaScriptFromString
这个是OC调用JS方法
self.navigationItem.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
直接调用就可以,缺点就是返回值只能是NSString,不够灵活。
- JavaScriptCore框架(iOS7)
如果你的项目最低支持到iOS7,那么可以尝试一下这个。
JavaScriptCore框架其实就是基于webkit中以C/C++实现的JavaScriptCore的一个包装
而JavaScriptCore是webkit中用来渲染JS的引擎,Safari浏览器和iOS开发中的webView都是由webkit来驱动的。
所以利用这个框架可以让 Objective-C 和 JavaScript 代码直接的交互变得更加的简单方便。
使用(注意:一定要在webView加载完成之后使用)
// OC调用JS方法
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
JSContext *jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
JSValue *value = [self.jsContext evaluateScript:@"document.title"];
self.navigationItem.title = value.toString;
}
// JS调用OC方法
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
self.jsContext[@"share"] = ^() {
//获取到share里的所有参数
NSArray *args = [JSContext currentArguments];
//args中的元素是JSValue,需要转成OC的对象
NSMutableArray *messages = [NSMutableArray array];
for (JSValue *obj in args) {
[messages addObject:[obj toObject]];
}
NSLog(@"点击分享js传回的参数:\n%@", messages);
};
}
- WebViewJavascriptBridge(第三方框架)
传送门:https://github.com/marcuswestin/WebViewJavascriptBridge/
如果你的项目想要支持iOS7之前的版本,同时想要拥有更好的交互体验,建议集成一下这个第三方框架。
作为github上8千多star的第三方框架,WebViewJavascriptBridge作为UIWebView交互工具无疑是极为优秀的,使用也极为简单。
OC代码
JS调用OC方法(JS给OC传数据)
//基本设置
[WebViewJavascriptBridge enableLogging];//开启日志
WebViewJavascriptBridge* bridge = [WebViewJavascriptBridge bridgeForWebView:self.webView];
[bridge setWebViewDelegate:self];
//方法注册,AppClosePrice为方法名
[bridge registerHandler:@"AppClosePrice" handler:^(id data, WVJBResponseCallback responseCallback) {
//data为JS传来的数据
NSLog(@"%@", data);
[self.navigationController popViewControllerAnimated:YES];
}];
** OC调用JS方法(OC给JS传数据,也可以接收JS传过来的数据)**
基本设置同上,就是注册方法的时候不同。
//AppHidden:方法名,appCurVersion:OC给JS传的数据
[self.bridge callHandler:@"AppHidden" data:appCurVersion responseCallback:^(id responseData) {
NSLog(@"----JS传过来的数据----%@",responseData);
}];
JS代码(由后台实现,可以给后台参考)
/*这段代码是固定的,必须要放到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方法都要放在此处注册,才能调用通过JS调用OC或者让OC调用这里的JS*/
setupWebViewJavascriptBridge(function(bridge) {
var uniqueId = 1
function log(message, data) {
var log = document.getElementById('log')
var el = document.createElement('div')
el.className = 'logLine'
el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
if (log.children.length) {
log.insertBefore(el, log.children[0])
} else {
log.appendChild(el)
}
}
/* Initialize your app here */
/*注册一个JS调用OC的方法,不带参数*/
bridge.registerHandler('AppClosePrice', function() {
log("JS调用OC")
})
/*注册一个JS调用OC的方法,带参数*/
bridge.registerHandler('AppClosePrice', function(data, responseCallback) {
log("Get user information from OC: ", data)
})
/*注册一个OC调用JS的方法*/
bridge.callHandler('AppHidden', function(responseData) {
log("JS call ObjC's getUserIdFromObjC function, and js received response:", responseData)
})
WKWebView交互
WKWebView作为UIWebView的替代品,采用自身的机制进行与JS的交互。
注意:WKWebView不支持JavaScriptCore框架的方式进行JS的交互。
JS调用OC方法(JS给OC传数据)
交互的方法名定义好,OC,JS都需要进行注册。
OC端注册方法(jsCallNative)
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = [[WKUserContentController alloc] init];
[config.userContentController addScriptMessageHandler:self name:@"jsCallNative"];
WKWebView *webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - 14) configuration:config];
JS端注册方法(jsCallNative)
function payClick() {
window.webkit.messageHandlers.jsCallNative.postMessage({order_no:'201511120981234',channel:'wx',amount:1,subject:'粉色外套'});
}
OC端接收数据
WKWebView的协议WKScriptMessageHandler里面的方法,JS向OC传任何数据,OC都可以通过该方法接收,通过JS传过来的数据,我们可以调用相应OC的方法,从而达到JS调用OC方法的目的。
/**
* JS给原生传数据:JS调用原生的方法
*/
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
NSDictionary *dict = message.body;
NSString* action = [dict valueForKey:@"action"];
NSLog(@"----WKWebView交互----%@",action);
if ([action isEqualToString:@"action1"]) {
[self action1];
}
if ([action isEqualToString:@"action2"]) {
[self action2];
}
}
OC调JS的方法(OC向JS传数据)
OC端代码
payResult 为规定好的方法名,JS端用这个方法名接收数据。
// 将支付结果返回给js
//这里的payResult()是JS的语言
NSString *jsStr = [NSString stringWithFormat:@"payResult('%@')",@"支付成功"];
[self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"%@----%@",result, error);
}];
注意:当JS调用OC的时候也可以用这个方法给JS进行回传数据,和上面的WKScriptMessageHandler代理中的方法相结合,就是实现了OC和JS的数据交流
JS端代码
function payResult(str) {
asyncAlert(str);
document.getElementById("returnValue").value = str;
}
总结
UIWebView与JS交互:
- 需要支持iOS6系统及其以下的系统:
集成WebViewJavascriptBridge三方框架就好,方便更加灵活的交互。 - 只需要支持iOS7及其以上的系统:
采用系统自带的JavaScriptCore框架就能满足日常的交互功能了。
WKWebView与JS交互:
根据WKWebView所提供的API代理方法就可以实现灵活的交互了。