js(javascript)与ios(Objective-C)相互通信交互

摘要:
随着苹果SDK的不断升级,添加了越来越多的新功能。本文主要介绍从iOS6到现在Native和JavaScript之间的交互方法。首先,UIWebview&&iframe&&JavaScript˂=iOS6iOS6本机不提供js直接调用Objective-C的方法,而只能通过UIWebview的UIWebViewDelegate协议1(BOOL)webView:(UIWebview*)webViewsh

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

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

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

 
1
(BOOL)webView:(UIWebView*)webViewshouldStartLoadWithRequest:(NSURLRequest*)requestnavigationType:(UIWebViewNavigationType)navigationType

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

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;
}
 
1
2
使用方法
<input type="button"value="传个字典2"onclick="callOC2('testFunc',{'param1':76,'param2':155,'param3':76})" />

Objective-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
-(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
*
*/
        returnNO;
    }
    returnYES;
}
 
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*einelements){
        NSArray*pair=[e componentsSeparatedByString:@"="];
        [retval setObject:[pair objectAtIndex:1]?:@"" forKey:[pair objectAtIndex:0]?@:"nokey"];
    }
    returnretval;
}

2.Objective-C调用javascript

 
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();"];
}
 
1
2
3
4
//提交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];
}

(PS)如果你想去掉webview弹出的alert 中的来自XXX网页

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

 
1
2
3
4
5
6
7
NSArray*frames=[webView valueForKeyPath:@"documentView.webView.mainFrame.childFrames"];
[frames enumerateObjectsUsingBlock:^(idframe,NSUIntegeridx,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

 
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
@interfaceJSCallOCViewController: UIViewController<UIWebViewDelegate,TestJSExport>
 
1
2
3
4
5
6
7
-(void)pushViewController:(NSString*)view title:(NSString*)title
{
    Classsecond=NSClassFromString(view);
    idsecondVC=[[second alloc]init];
    ((UIViewController*)secondVC).title=title;
    [self.navigationController pushViewController:secondVC animated:YES];
}

JavaScript

 
1
<aid="push"href="#"onclick="app.pushViewControllerTitle('SecondViewController','secondPushedFromJS');">

2.Objective-C 调用 JavaScriptCore

Objective-C

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

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

JavaScript

 
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

 
1
2
3
4
5
6
//执行html 已经存在的js方法
-(IBAction)exeFuncTouched:(id)sender{
    [self.myWebView evaluateJavaScript:@"showAlert('hahahha')"  completionHandler:^(iditem,NSError*_Nullable error){
        
    }];
}

2. JavaScript 调用 Objective-C

JavaScript,简单的封装一下,‘Native’为事先在Objective-C注册注入的js对象

 
1
2
3
4
5
6
7
8
function callOC(func,param){
        var url="func="+func;
        for(variinparam)
        {
         url=url+"&"+i+"="+param[i];
        }
        window.webkit.messageHandlers.Native.postMessage(url);
}

JavaScript调用

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

Objective-C实现

 
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:%@",[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

免责声明:文章转载自《js(javascript)与ios(Objective-C)相互通信交互》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇ROS-节点参数paramMAC安装Flink-JAVA_HOME的错误,以及启动运行下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

一文梳理JS事件

JavaScript与HTML的交互是通过事件进行的。事件,就是文档或浏览器窗口发生的一些特定的交互瞬间。 事件流 事件捕获 事件冒泡 事件处理程序 事件委托 1. 事件流 如果单机页面上的某个按钮,认为单击事件不仅仅发生在按钮上。火炬话说,在单击按钮的同时,你也单击的按钮的容器元素,甚至也单击了整个页面。 事件流描述的是从页面中接收事件的顺序。...

JS高级---把局部变量变成全局变量

如何把局部变量变成全局变量?  把局部变量给window就可以了     函数的自调用---自调用函数 一次性的函数--声明的同时, 直接调用了 (function () { console.log("函数"); })(); 局部变量 页面加载后.这个自调用函数的代码就执行完了 (function (形参) {...

Cocos2d-js和Android交互

说白了,就是JavaScript和Java之间的函数互相调用。 先看一下效果 有了这个交互,为了以后接sdk做准备。 要点: javascript调用java: jsb.reflection.callStaticMethod("org/cocos2dx/javascript/AppActivity", "hello", "(Ljava/lang/Stri...

W5500问题集锦(持续更新中)

在”WIZnet杯”以太网技术竞赛中,有非常多參赛者在使用中对W5500有各种各样的疑问,对于这款WIZnet新推出的以太网芯片,使用中大家是不是也一样存在下面问题呢?来看一看: 1. W5500不支持自己主动极性变换,有点失望…… 答: 事实上,仅仅要对方支持极性变换就能够实现,如今的设备不支持极性变换的非常少的。 你要是碰到个别老设备连不上,再...

JSON.stringify 语法讲解 随风浪迹天涯 博客园

JSON.stringify 语法讲解 认识javascript也不短的时间了,可是这个用法说实在的,我还是第一次见过,惭愧啊惭愧啊。于是乎,在网上找了写资料,写了些例子 希望能给园子们一些帮助。 作用:这个函数的作用主要是为了系列化对象的。 可能有些人对系列化这个词过敏,我的理解很简单。就是说把原来是对象的类型转换成字符串类型(或者更确切的说是json...

如何通过Html网页调用本地安卓app?

如何使用html网页和本地app进行传递数据呢?经过研究,发现还是有方法的,总结了一下,大致有一下几种方式 一、通过html页面打开Android本地的app 1、首先在编写一个简单的html页面 <html> <head> <meta http-equiv="Content-Type" c...