iOS之与JS交互通信

随着苹果SDK的不断升级,越来越多的新特性增加了进来,本文主要讲述从iOS6至今,Native与JavaScript的交互方法

一、UIWebview && iframe && JavaScript  <=iOS6

iOS6原生没有提供js直接调用Objective-C的方式,只能通过UIWebView的UIWebViewDelegate协议

(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

1

(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

方法来做拦截,并在这个方法中,根据url来调用Objective-C方法

1.javascript调用Objective-C

动态添加个iframe改变其地址 最后删除,这种方法不会使当前页面跳转 效果更佳

javascript代码:

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;
}

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代码:

- (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;
}

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;

}

- (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;
}

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

//插入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();"];
}

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();"];

}

//提交form表单
- (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网页

- (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

5

- (void)webViewDidFinishLoad: (UIWebView *) webView

{

//重定义web的alert方法,捕获webview弹出的原生alert  可以修改标题和内容等等

[webView stringByEvaluatingJavaScriptFromString:@"window.alert = function(message) { window.location = \"myapp:&func=alert&message=\" + message; }"];

}

if([func isEqualToString:@"alert"])
{
[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问题

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

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代码

<input type="button" value="多参数调用" onclick="mutiParams(‘参数1‘,‘参数2‘,‘参数3‘);" />

1

<input type="button" value="多参数调用" onclick="mutiParams(‘参数1‘,‘参数2‘,‘参数3‘);" />

iOS中的代码  UIWebview的delegate

- (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

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

@interface JSCallOCViewController : UIViewController<UIWebViewDelegate,TestJSExport>

1

@interface JSCallOCViewController : UIViewController<UIWebViewDelegate,TestJSExport>

- (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

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

<a id="push" href="#" onclick="app.pushViewControllerTitle(‘SecondViewController‘,‘secondPushedFromJS‘);">

1

<a id="push" href="#" onclick="app.pushViewControllerTitle(‘SecondViewController‘,‘secondPushedFromJS‘);">

2.Objective-C 调用 JavaScriptCore

Objective-C

调用js的showResult方法,这里是一个参数 result,多个就依次写到数组中

[self.context[@"showResult"] callWithArguments:@[result]];

1

[self.context[@"showResult"] callWithArguments:@[result]];

JavaScript

function showResult(resultNumber)
{
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

//执行html 已经存在的js方法
- (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对象

function callOC(func,param){
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调用

<input type="button" value="打个招呼" onclick="callOC(‘alert‘,{‘message‘:‘你好么‘})" />

1

<input type="button" value="打个招呼" onclick="callOC(‘alert‘,{‘message‘:‘你好么‘})" />

Objective-C实现

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];

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];

#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"]];
}

}
}

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

时间: 2024-08-11 15:09:40

iOS之与JS交互通信的相关文章

iOS web与JS交互

webview 与 JS交互核心代码为: [webView stringByEvaluatingJavaScriptFromString:@"xxxxxxxxxx"];

iOS HTML5的JS交互

 //webView与H5点击交互走的代理   -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSURL * url = [request URL]; NSString * urlStr = url.absoluteString; NSString * pr

【REACT NATIVE 系列教程之十二】REACT NATIVE(JS/ES)与IOS(OBJECT-C)交互通信

一用到跨平台的引擎必然要有引擎与各平台原生进行交互通信的需要.那么Himi先讲解React Native与iOS之间的通信交互. 本篇主要分为两部分讲解:(关于其中讲解的OC语法等不介绍,不懂的请自行学习) 1. React Native 访问iOS 2. iOS访问React Native     一:React Native 访问iOS 1. 我们想要JS调用OC函数,就要实现一个"RCTBridgeModule"协议的Objective-C类 所以首先我们先创建一个oc新类,  

ios的UIWebView与js交互的有关知识

一.UIWebView与JavaScript的交互 源地址:http://blog.sina.com.cn/s/blog_74e9d98d010199jc.html 总体概述:       实质上oc与js的通信交互就是发送消息,也即函数调用,只要在交互的过程正确的指定好对方需要调用的函数和参数就ok oc-->js  stringByEvaluatingJavaScriptFromString,其参数是一NSString 字符串内容是js代码(这又可以是一个js函数.一句js代码或他们的组合)

IOS Webview JS交互之事件拦截获取

最近项目中用到了webvVew,webView加载的网页中有两个按钮,要在app里面响应这两个按钮的点击事件,并接受JS传过来的参数. ios7 的时候,出来了一个新的类库叫JavaScriptCore.framework - (void)setupJsContent { //获取当前JS环境 _content = [_webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; // 打印异

iOS UIWebView与JS(JavaScript)交互-----根据节点隐藏HTML中内容

在移动端(这里指iOS)开发中,我们免不了使用UIWebView与JS交互. 先简单了解一下JS,JavaScript是一种直译式的脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.广泛用于客户端,主要用来向HTML页面添加交互行为.它可以直接嵌入HTML页面,但写成单独的JS文件有利于结构和行为的分离.JS具有跨平台特性,在绝大多数浏览器的支持下,可以在多种平台下运行(如Windows.Linux.Mac.Andriod.iOS等).作为直译语言,它的弱点:安全性. -- 摘自

iOS JS 交互之利用系统JSContext实现 JS调用oc方法

ios js 交互分为两块: 1.oc调用js 这一块实现起来比较简单, 我的项目中加载的是本地的html,js,css,需要注意的是当你向工程中拖入这些文件时,选择如下操作,(拖入的文件夹是蓝色的,相对路径),不然css,js 的路径会存在问题 加载本地html: oc调用js:一句代码搞定 2.js 调用oc js调用oc又分为两种: 1.js端是直接调用方法 这里就要说到ios7才推出的一个新的api    JavaScriptCore,首先我们引入这个类,并初始化一个JSContext对

Ios开发之 -- js和ios的交互

==WebViewJavascriptBridge的介绍== #下载:https://github.com/marcuswestin/WebViewJavascriptBridge #关于WebViewJavascriptBridge的介绍:http://blog.csdn.net/yanghua_kobe/article/details/8209751 ==WebViewJavascriptBridge(在与现有的业务代码结合使用中)的小问题== *demo部分(  ExampleApp.ht

iOS与JS交互

① iOS -> JS 原理:通过WebView的方法直接调用JS代码 [WebView stringByEvaluatingJavaScriptFromString:@"A"]; // 这里的 A 就是WebView中的html所对应的JS代码 eg. 1)这里一个html所包含的js文件内的一个public方法 2)这是一个.m(iOS)文件内的一个WebView的调用1)中js方法的写法 3)那么在js的对应方法中,就会得到来自iOS的参数 ② JS -> iOS 原