iOS之与JS交互通信
随着苹果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
|
方法来做拦截,并在这个方法中,根据url来调用Objective-C方法
1.javascript调用Objective-C
动态添加个iframe改变其地址 最后删除,这种方法不会使当前页面跳转 效果更佳
javascript代码:
var iframe = document.createElement("iframe");
var url= "myapp:" + "&func=" + func;
for(var i in param)
{
url = url + "&" + i + "=" + param[i];
}
iframe.src = url;
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.parentNode.removeChild(iFrame);
iframe = null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function callOC2(func,param){
var iframe = document.createElement("iframe");
var url= "myapp:" + "&func=" + func;
for(var i in param)
{
url = url + "&" + i + "=" + param[i];
}
iframe.src = url;
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.parentNode.removeChild(iFrame);
iframe = null;
}
|
<input type="button" value="传个字典2" onclick="callOC2('testFunc',{'param1':76,'param2':155,'param3':76})" />
1
2
|
使用方法
<input type="button" value="传个字典2" onclick="callOC2('testFunc',{'param1':76,'param2':155,'param3':76})" />
|
Objective-C代码:
NSString *requestString = [[[request URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding ];
if ([requestString hasPrefix:@"myapp:"]) {
NSLog(@"requestString:%@",requestString);
//如果是自己定义的协议, 再截取协议中的方法和参数, 判断无误后在这里手动调用oc方法
NSMutableDictionary *param = [self queryStringToDictionary:requestString];
NSLog(@"get param:%@",[param description]);
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
*
*/
return NO;
}
return YES;
}
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 = [[[request URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding ];
if ([requestString hasPrefix:@"myapp:"]) {
NSLog(@"requestString:%@",requestString);
//如果是自己定义的协议, 再截取协议中的方法和参数, 判断无误后在这里手动调用oc方法
NSMutableDictionary *param = [self queryStringToDictionary:requestString];
NSLog(@"get param:%@",[param description]);
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
*
*/
return NO;
}
return YES;
}
|
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;
}
1
2
3
4
5
6
7
8
9
|
- (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.Objective-C调用javascript
- (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();"];
}
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();"];
}
|
- (IBAction)submitTouched:(id)sender {
[self.myWeb stringByEvaluatingJavaScriptFromString:@"document.forms[0].submit(); "];
}
1
2
3
4
|
//提交form表单
- (IBAction)submitTouched:(id)sender {
[self.myWeb stringByEvaluatingJavaScriptFromString:@"document.forms[0].submit(); "];
}
|
- (IBAction)fontTouched:(id)sender {
NSString *tempString2 = [NSString stringWithFormat:@"document.getElementsByTagName('p')[0].style.fontSize='%@';",@"19px"];
[self.myWeb stringByEvaluatingJavaScriptFromString:tempString2];
}
1
2
3
4
5
|
//修改标签属性
- (IBAction)fontTouched:(id)sender {
NSString *tempString2 = [NSString stringWithFormat:@"document.getElementsByTagName('p')[0].style.fontSize='%@';",@"19px"];
[self.myWeb stringByEvaluatingJavaScriptFromString:tempString2];
}
|
(PS)如果你想去掉webview弹出的alert 中的来自XXX网页
{
//重定义web的alert方法,捕获webview弹出的原生alert 可以修改标题和内容等等
[webView stringByEvaluatingJavaScriptFromString:@"window.alert = function(message) { window.location = \"myapp:&func=alert&message=\" + message; }"];
}
1
2
3
4
5
|
- (void)webViewDidFinishLoad: (UIWebView *) webView
{
//重定义web的alert方法,捕获webview弹出的原生alert 可以修改标题和内容等等
[webView stringByEvaluatingJavaScriptFromString:@"window.alert = function(message) { window.location = \"myapp:&func=alert&message=\" + message; }"];
}
|
{
[self showMessage:@"来自网页的提示" message:[param objectForKey:@"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问题
[frames enumerateObjectsUsingBlock:^(id frame, NSUInteger idx, BOOL *stop) {
JSContext *context = [frame valueForKeyPath:@"javaScriptContext"];
context[@"Window"][@"prototype"][@"alert"] = ^(NSString *message) {
NSLog(@"%@", message);
};
}];
1
2
3
4
5
6
7
|
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
|
<input type="button" value="多参数调用" onclick="mutiParams('参数1','参数2','参数3');" />
|
iOS中的代码 UIWebview的delegate
{
// 以 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
- (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);
};
}
|
JSExport 协议关联 native 的方法
Objective-C
1
|
@interface JSCallOCViewController : UIViewController<UIWebViewDelegate,TestJSExport>
|
{
Class second = NSClassFromString(view);
id secondVC = [[second alloc]init];
((UIViewController*)secondVC).title = title;
[self.navigationController pushViewController:secondVC animated:YES];
}
1
2
3
4
5
6
7
|
- (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];
}
|
JavaScript
1
|
<a id="push" href="#" onclick="app.pushViewControllerTitle('SecondViewController','secondPushedFromJS');">
|
2.Objective-C 调用 JavaScriptCore
Objective-C
调用js的showResult方法,这里是一个参数 result,多个就依次写到数组中
1
|
[self.context[@"showResult"] callWithArguments:@[result]];
|
JavaScript
{
document.getElementById("result").innerText = resultNumber;
}
1
2
3
4
|
function showResult(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
- (IBAction)exeFuncTouched:(id)sender {
[self.myWebView evaluateJavaScript:@"showAlert('hahahha')" completionHandler:^(id item, NSError * _Nullable error) {
}];
}
1
2
3
4
5
6
|
//执行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对象
var url= "func=" + func;
for(var i in param)
{
url = url + "&" + i + "=" + param[i];
}
window.webkit.messageHandlers.Native.postMessage(url);
}
1
2
3
4
5
6
7
8
|
function callOC(func,param){
var url= "func=" + func;
for(var i in param)
{
url = url + "&" + i + "=" + param[i];
}
window.webkit.messageHandlers.Native.postMessage(url);
}
|
JavaScript调用
1
|
<input type="button" value="打个招呼" onclick="callOC('alert',{'message':'你好么'})" />
|
Objective-C实现
config.userContentController = [[WKUserContentController alloc] init];
// 注入JS对象Native,
// 声明WKScriptMessageHandler 协议
[config.userContentController addScriptMessageHandler:self name:@"Native"];
self.myWebView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
self.myWebView.UIDelegate = self;
[self.view addSubview:self.myWebView];
1
2
3
4
5
6
7
8
9
|
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = [[WKUserContentController alloc] init];
// 注入JS对象Native,
// 声明WKScriptMessageHandler 协议
[config.userContentController addScriptMessageHandler:self name:@"Native"];
self.myWebView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
self.myWebView.UIDelegate = self;
[self.view addSubview:self.myWebView];
|
- (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:%@",[param description]);
NSString *func = [param objectForKey:@"func"];
//调用本地函数
if([func isEqualToString:@"alert"])
{
[self showMessage:@"来自网页的提示" message:[param objectForKey:@"message"]];
}
}
}
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:%@",[param description]);
NSString *func = [param objectForKey:@"func"];
//调用本地函数
if([func isEqualToString:@"alert"])
{
[self showMessage:@"来自网页的提示" message:[param objectForKey:@"message"]];
}
}
}
|
注:本文除了第三种方法之外,前两种JavaScript交互方法都和Android开发兼容,仅仅是api略不同。
demo地址: https://github.com/shaojiankui/iOS-WebView-JavaScript
iOS之与JS交互通信的更多相关文章
- VsCode插件与Node.js交互通信
首先关于VsCode插件通信,如果不明白的可以参考我的这篇博客VsCode插件开发之插件初步通信 如果需要详细例子的话,可以参考VsCode插件开发 现在又有一个新的需求是,VsCode插件可以通过j ...
- iOS webView与js交互在文本空格上输入文字
项目要求:webview加载html网址,内容为填空题型文本,需要在横线上添加答案,并点击提交按钮后再将答案进行回显 正常加载的效果图片: 这个是用js交互后的效果图: 点击空格,输入想输入的答案,如 ...
- ios WKWebView 与 JS 交互实战技巧
一.WKWebView 由于Xcode8发布之后,编译器开始不支持iOS 7了,这样我们的app也改为最低支持iOS 8.0,既然需要与web交互,那自然也就选择使用了 iOS 8.0之后 才推出的新 ...
- andriod/ios webview与js交互 html_demo
<html> <head> <title>测试</title> </head> <body> <h3>Android ...
- IOS UIWebView与js的简单交互swift3版
在开发过程中,我们可能遇到ios代码与js交互的情况,本人第一次使用遇到了很多坑,这里纪录一下,方便自己,也方便需要的人. 1.第一步先建一个接口(协议)并继承JSExport 这里实现两个方法提供给 ...
- UIWebView 与 JS 交互(1):Objective-C 调用 Javascript
众所周知,随着硬件水平的发展,HTML5 与原生 APP 性能差距不断缩小,正在互联网科技领域扮演者越来越重要的角色.作为一种能很大程度上节约成本的技术方案,通过 HTML5 及 JS 实现的跨平台技 ...
- 【REACT NATIVE 系列教程之十二】REACT NATIVE(JS/ES)与IOS(OBJECT-C)交互通信
http://blog.csdn.net/xiaominghimi/article/details/51586492 一用到跨平台的引擎必然要有引擎与各平台原生进行交互通信的需要.那么Himi先讲解R ...
- 李洪强iOS经典面试题147-WebView与JS交互
李洪强iOS经典面试题147-WebView与JS交互 WebView与JS交互 iOS中调用HTML 1. 加载网页 NSURL *url = [[NSBundle mainBundle] UR ...
- iOS原生APP与H5+JS交互////////////////////zzzz
原生代码中直接加载页面 1. 具体案例 加载本地/网络HTML5作为功能介绍页 2. 代码示例 //本地 -(void)loadLocalPage:(UIWebView*)webView ...
随机推荐
- MapReduce Kmeans聚类算法
最近在网上查看用MapReduce实现的Kmeans算法,例子是不错,http://blog.csdn.net/jshayzf/article/details/22739063 但注释太少了,而且参数 ...
- iOS 8 Xcode6 设置Launch Image 启动图片<转>
Step1 1.点击Image.xcassets 进入图片管理,然后右击,弹出"New Launch Image" 2.如图,右侧的勾选可以让你选择是否要对ipad,横屏,竖屏,以 ...
- 第二百六十六天 how can I 坚持
有些小郁闷,上班没事干,技术.要买房,还要结婚,买了房哪还有彩礼钱,还得借钱,愁人. 该咋办,毕梦琪. 哎.今天还和程秀通说好的去看房呢,整天犹豫不定的.烦. 今天平安夜啊.吃了个苹果,一生平平安安, ...
- 利用phantomjs模拟QQ自动登录
之前为了抓取兴趣部落里的数据,研究了下QQ自动登录. 当时搜索了一番,发现大部分方法都已经失效了,于是准备自己开搞. 第一个想到的就是参考网上已有方案的做法,梳理登陆js的实现,通过其他语言重写.考虑 ...
- 【转】并发编程之Operation Queue
http://blog.xcodev.com/blog/2013/10/28/operation-queue-intro/ 随着移动设备的更新换代,移动设备的性能也不断提高,现在流行的CPU已经进入双 ...
- CodeForces 689E Mike and Geometry Problem (离散化+组合数)
Mike and Geometry Problem 题目链接: http://acm.hust.edu.cn/vjudge/contest/121333#problem/I Description M ...
- Spring Auto proxy creator example
In last Spring AOP examples – advice, pointcut and advisor, you have to manually create a proxy bean ...
- linux 查看当前所在目录的全路径
有时候,使用linux的shell的时候需要查看当前位置的全路径,可以使用 pwd命令 当然,知道了该命令就可以通过man pwd来查看该命令的全部帮助手册.
- .NET的Snk使用方法
保护你Asp.Net生成的DLL和Code不被别人反编译 大家做项目开发一般都是分层的,比如UI层,业务层,数据访问层.业务层引用数据访问层的DLL(比如 dataAccess.dll),并使用da ...
- <select>改造成<s:select>实现表单的回显功能
初始: <select name="viewType"> <option value="0">全部主题</option> & ...