js(javascript)与ios(Objective-C)相互通信交互
2016-06-13 12:26
337 查看
转自 http://www.skyfox.org/javascript-ios-navive-message.html
随着苹果SDK的不断升级,越来越多的新特性增加了进来,本文主要讲述从iOS6至今,Native与JavaScript的交互方法
iOS6原生没有提供js直接调用Objective-C的方式,只能通过UIWebView的UIWebViewDelegate协议
方法来做拦截,并在这个方法中,根据url来调用Objective-C方法
Objective-C代码:
(PS)如果你想去掉webview弹出的alert 中的来自XXX网页
iOS7中加入了JavaScriptCore.framework框架。把 WebKit 的 JavaScript 引擎用 Objective-C 封装。该框架让Objective-C和JavaScript代码直接的交互变得更加的简单方便。
分两种方式
第一在渲染网页时遇到<script标签时,就会创建JSContext环境去运行JavaScript代码。
第二就是使用方法[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]去获取JSContext环境时,这时无论是否遇到<script标签,都会去创造出来一个JSContext环境,而且和遇到<script标签再创造环境是同一个。
我通常都会在 - (void)webViewDidFinishLoad:(UIWebView *)webView中去注入交互对象,但是这时候网页还没加载完,JavaScript那边已经调用交互方法,这样就会调不到原生应用的方法而出现问题。
改成在- (void)viewDidLoad中去注入交互对象,这样倒是解决了上面的问题,但是同时又引起了一个新的问题就是在一个网页内部点击链接跳转到另一个网页的时候,第二个页面需要交互,这时JSContext环境已经变化,但是- (void)viewDidLoad仅仅加载一次,跳转的时候,没有再次注入交互对象,这样就会导致第二个页面没法进行交互。当然你可以在- (void)viewDidLoad和- (void)webViewDidFinishLoad:(UIWebView *)webView都注入一次,但是一定会有更优雅的办法去解决此问题。
如果上边的方案能满足需求,建议实在迫不得已再用这个方法, 就是在每次创建JSContext环境的时候,我们都去注入此交互对象这样就解决了上面的问题。具体解决办法参考了此开源库UIWebView-TS_JavaScriptContext。
iOS中的代码 UIWebview的delegate
JSExport 协议关联 native 的方法Objective-C
JavaScript
Objective-C
调用js的showResult方法,这里是一个参数 result,多个就依次写到数组中
JavaScript
iOS 8引入了一个新的框架——WebKit,之后变得好起来了。在WebKit框架中,有WKWebView可以替换UIKit的UIWebView和AppKit的WebView,而且提供了在两个平台可以一致使用的接口。WebKit框架使得开发者可以在原生App中使用Nitro来提高网页的性能和表现,Nitro就是Safari的JavaScript引擎 WKWebView 不支持JavaScriptCore的方式但提供message handler的方式为JavaScript 与Native通信.
1.Objective-C 调用JavaScript
JavaScript调用
Objective-C实现
注:本文除了第三种方法之外,前两种JavaScript交互方法都和Android开发兼容,仅仅是api略不同。
demo地址: https://github.com/shaojiankui/iOS-WebView-JavaScript
随着苹果SDK的不断升级,越来越多的新特性增加了进来,本文主要讲述从iOS6至今,Native与JavaScript的交互方法
一、UIWebview && iframe && JavaScript <=iOS6
iOS6原生没有提供js直接调用Objective-C的方式,只能通过UIWebView的UIWebViewDelegate协议1 | (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType |
1.javascript调用Objective-C
动态添加个iframe改变其地址 最后删除,这种方法不会使当前页面跳转 效果更佳javascript代码:1 2 3 4 5 6 7 8 9 10 11 12 13 | functioncallOC2(func,param){ variframe=document.createElement("iframe"); varurl="myapp:"+"&func="+func; for(variinparam) { url=url+"&"+i+"="+param[i]; } iframe.src=url; iframe.style.display='none'; document.body.appendChild(iframe); iframe.parentNode.removeChild(iFrame); iframe=null; } |
12 | 使用方法<input type="button" value="传个字典2" onclick="callOC2('testFunc',{'param1':76,'param2':155,'param3':76})" /> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | -(BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType{ NSString*requestString=[[[requestURL] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; if([requestString hasPrefix:@"myapp:"]){ NSLog(@"requestString:%@",requestString); //如果是自己定义的协议, 再截取协议中的方法和参数, 判断无误后在这里手动调用oc方法 NSMutableDictionary*param=[self queryStringToDictionary:requestString]; NSLog(@"get param:%@",[paramdescription]); NSString*func=[param objectForKey:@"func"]; if([func isEqualToString:@"callFunc"]) { [self testFunc:[param objectForKey:@"first"] withParam2:[param objectForKey:@"second"] andParam3:[param objectForKey:@"third"]]; } /* * 方法的返回值是BOOL值。 * 返回YES:表示让浏览器执行默认操作,比如某个a链接跳转 * 返回NO:表示不执行浏览器的默认操作,这里因为通过url协议来判断js执行native的操作,肯定不是浏览器默认操作,故返回NO * */ returnNO; } returnYES; } |
123456789 | - (NSMutableDictionary*)queryStringToDictionary:(NSString*)string { NSMutableArray *elements = (NSMutableArray*)[string componentsSeparatedByString:@"&"]; NSMutableDictionary *retval = [NSMutableDictionary dictionaryWithCapacity:[elements count]]; for(NSString *e in elements) { NSArray *pair = [e componentsSeparatedByString:@"="]; [retval setObject:[pair objectAtIndex:1]?:@"" forKey:[pair objectAtIndex:0]?@:"nokey"]; } return retval;} |
2.[b]Objective-C调用javascript[/b]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //插入js 并且执行传值 -(IBAction)insertJSTouched:(id)sender{ NSString*insertString=[NSString stringWithFormat: @"var script = document.createElement('script');" "script.type = 'text/javascript';" "script.text = \"function jsFunc() { " "var a=document.getElementsByTagName('body')[0];" "alert('%@');" "}\";" "document.getElementsByTagName('head')[0].appendChild(script);",self.someString]; NSLog(@"insert string %@",insertString); [self.myWeb stringByEvaluatingJavaScriptFromString:insertString]; [self.myWeb stringByEvaluatingJavaScriptFromString:@"jsFunc();"]; } |
1234 | //提交form表单- (IBAction)submitTouched:(id)sender { [self.myWeb stringByEvaluatingJavaScriptFromString:@"document.forms[0].submit(); "];} |
1 2 3 4 5 | //修改标签属性 -(IBAction)fontTouched:(id)sender{ NSString*tempString2=[NSString stringWithFormat:@"document.getElementsByTagName('p')[0].style.fontSize='%@';",@"19px"]; [self.myWeb stringByEvaluatingJavaScriptFromString:tempString2]; } |
12345 | - (void)webViewDidFinishLoad: (UIWebView *) webView{ //重定义web的alert方法,捕获webview弹出的原生alert 可以修改标题和内容等等 [webView stringByEvaluatingJavaScriptFromString:@"window.alert = function(message) { window.location = \"myapp:&func=alert&message=\" + message; }"];} |
1 2 3 4 | if([func isEqualToString:@"alert"]) { [self showMessage:@"来自网页的提示" message:[param objectForKey:@"message"]]; } |
二、JavaScriptCore && UIWebview >=iOS7
iOS7中加入了JavaScriptCore.framework框架。把 WebKit 的 JavaScript 引擎用 Objective-C 封装。该框架让Objective-C和JavaScript代码直接的交互变得更加的简单方便。
合适时机注入交互对象
什么时候UIWebView会创建JSContext环境?
分两种方式第一在渲染网页时遇到<script标签时,就会创建JSContext环境去运行JavaScript代码。
第二就是使用方法[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]去获取JSContext环境时,这时无论是否遇到<script标签,都会去创造出来一个JSContext环境,而且和遇到<script标签再创造环境是同一个。
什么时候注入JSContext问题
我通常都会在 - (void)webViewDidFinishLoad:(UIWebView *)webView中去注入交互对象,但是这时候网页还没加载完,JavaScript那边已经调用交互方法,这样就会调不到原生应用的方法而出现问题。改成在- (void)viewDidLoad中去注入交互对象,这样倒是解决了上面的问题,但是同时又引起了一个新的问题就是在一个网页内部点击链接跳转到另一个网页的时候,第二个页面需要交互,这时JSContext环境已经变化,但是- (void)viewDidLoad仅仅加载一次,跳转的时候,没有再次注入交互对象,这样就会导致第二个页面没法进行交互。当然你可以在- (void)viewDidLoad和- (void)webViewDidFinishLoad:(UIWebView *)webView都注入一次,但是一定会有更优雅的办法去解决此问题。
如果上边的方案能满足需求,建议实在迫不得已再用这个方法, 就是在每次创建JSContext环境的时候,我们都去注入此交互对象这样就解决了上面的问题。具体解决办法参考了此开源库UIWebView-TS_JavaScriptContext。
多个iFrame中的JSContext问题
1234567 | NSArray *frames = [webView valueForKeyPath:@"documentView.webView.mainFrame.childFrames"];[frames enumerateObjectsUsingBlock:^(id frame, NSUInteger idx, BOOL *stop) { JSContext *context = [frame valueForKeyPath:@"javaScriptContext"]; context[@"Window"][@"prototype"][@"alert"] = ^(NSString *message) { NSLog(@"%@", message); };}]; |
1. JavaScriptCore调用Objective-C
html中的JS代码1 | <inputtype="button"value="多参数调用"onclick="mutiParams('参数1','参数2','参数3');"/> |
12345678910111213141516171819202122232425262728293031 | - (void)webViewDidFinishLoad:(UIWebView *)webView{ // 以 html title 设置 导航栏 title self.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"]; // Undocumented access to UIWebView's JSContext self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; // 打印异常 self.context.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) { context.exception = exceptionValue; NSLog(@"%@", exceptionValue); }; // 以 JSExport 协议关联 native 的方法 self.context[@"app"] = self; // 以 block 形式关联 JavaScript function self.context[@"log"] = ^(NSString *str) { NSLog(@"%@", str); }; //多参数 self.context[@"mutiParams"] = ^(NSString *a,NSString *b,NSString *c) { NSLog(@"%@ %@ %@",a,b,c); }; } |
1 | @interfaceJSCallOCViewController: UIViewController<UIWebViewDelegate,TestJSExport> |
1234567 | - (void)pushViewController:(NSString *)view title:(NSString *)title{ Class second = NSClassFromString(view); id secondVC = [[second alloc]init]; ((UIViewController*)secondVC).title = title; [self.navigationController pushViewController:secondVC animated:YES];} |
1 | <aid="push"href="#"onclick="app.pushViewControllerTitle('SecondViewController','secondPushedFromJS');"> |
2.Objective-C 调用 JavaScriptCore
Objective-C调用js的showResult方法,这里是一个参数 result,多个就依次写到数组中
1 | [self.context[@"showResult"] callWithArguments:@[result]]; |
1 2 3 4 | functionshowResult(resultNumber) { document.getElementById("result").innerText=resultNumber; } |
三、WKWebView && JavaScript >=iOS8
iOS 8引入了一个新的框架——WebKit,之后变得好起来了。在WebKit框架中,有WKWebView可以替换UIKit的UIWebView和AppKit的WebView,而且提供了在两个平台可以一致使用的接口。WebKit框架使得开发者可以在原生App中使用Nitro来提高网页的性能和表现,Nitro就是Safari的JavaScript引擎 WKWebView 不支持JavaScriptCore的方式但提供message handler的方式为JavaScript 与Native通信.1.Objective-C 调用JavaScript
123456 | //执行html 已经存在的js方法- (IBAction)exeFuncTouched:(id)sender { [self.myWebView evaluateJavaScript:@"showAlert('hahahha')" completionHandler:^(id item, NSError * _Nullable error) { }];} |
2. JavaScript 调用 Objective-C
JavaScript,简单的封装一下,‘Native’为事先在Objective-C注册注入的js对象1 2 3 4 5 6 7 8 | functioncallOC(func,param){ varurl="func="+func; for(variinparam) { url=url+"&"+i+"="+param[i]; } window.webkit.messageHandlers.Native.postMessage(url); } |
1 | <input type="button" value="打个招呼" onclick="callOC('alert',{'message':'你好么'})" /> |
1 2 3 4 5 6 7 8 9 | WKWebViewConfiguration*config=[[WKWebViewConfigurationalloc] init]; config.userContentController=[[WKUserContentControlleralloc] init]; // 注入JS对象Native, // 声明WKScriptMessageHandler 协议 [config.userContentController addScriptMessageHandler:self name:@"Native"]; self.myWebView=[[WKWebViewalloc] initWithFrame:self.view.bounds configuration:config]; self.myWebView.UIDelegate=self; [self.view addSubview:self.myWebView]; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #pragma mark - WKScriptMessageHandler -(void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message{ if([message.name isEqualToString:@"Native"]){ NSLog(@"message.body:%@",message.body); //如果是自己定义的协议, 再截取协议中的方法和参数, 判断无误后在这里手动调用oc方法 NSMutableDictionary*param=[self queryStringToDictionary:message.body]; NSLog(@"get param:%@",[paramdescription]); NSString*func=[param objectForKey:@"func"]; //调用本地函数 if([func isEqualToString:@"alert"]) { [self showMessage:@"来自网页的提示" message:[param objectForKey:@"message"]]; } } } |
demo地址: https://github.com/shaojiankui/iOS-WebView-JavaScript
相关文章推荐
- objective-C之NSDate相关类(四)
- objective-C之NSDate相关类(三)
- Windows下的Objective-C集成开发环境(IDE)
- objective-C之NSDate相关类(二)
- PHP 将json的stdClass Object转成数组array 方法二
- Objective-C --- - UITableView 二 对一进行一些属性补充(梳理总结)
- Object 泛型
- Android Studio安装后运行程序时提示Could not reserve enough space for object?
- 论文笔记《Rich Feature Hierarchies for accurate object detection and semantic segmentation》
- Objective-C仿映客跑车动画
- hive脚本出现Error: java.lang.RuntimeException: Error in configuring object和Caused by: java.lang.IndexOutOfBoundsException: Index: 9, Size: 9
- Swift与Object-C的区别
- objective-C之NSDate相关类(一)
- 网页中通过swfobject插入Flash动画以及设置透明
- YYModel 源码解读(二)之NSObject+YYModel.h (1)
- Cannot use object of type yii\db\Connection as array
- All object files and libraries for bitcode must be generated from...
- caffe_.mexa64': libcaffe.so: cannot open shared object file: No such file or directory
- wish8.6: error while loading shared libraries: libXss.so.1: cannot open shared object file: No such
- 理解 Objective-C Runtime