iOS与JS交互总结

iOS与JS交互总结

近几年来移动开发使用网页嵌入形式的越来越多,这就不可避免的出现原生控件和网页页面的JS交互,本篇就大概总结一下目前iOS开发中原生控件与JS的交互的几种形式。</br>

iOS开发中使用的是UIWebView控件来加载网页页面资源的。所以我们也就主要围绕这个控件来总结一下,大体上可以分为三种形式:

1、原生api交互,直接执行脚本,拦截代理
2、第三方库WebViewJavascriptBrige交互,实质还是拦截代理
3、JavaScriptCore框架
  3.1 context上下文,context上下文直接设置和调用
  3.2 JSExport协议,通过JSExport协议设置和调用


接下来就一个一个的来看看到底是如何实现的,大家也可以提前把demo下载下来,结合代码来看效果会更好哦。

原生api交互

使用原生API来实现交互,实际上主要用到一个函数和一个协议,
函数:

- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

协议:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
1、 OBJC调用JS

直接使用UIWebView的函数执行JS代码,
比如我们JS代码中有一个函数objcCallJS,

function objcCallJS() {
        var data = 'ljt'
        alert('来自objc的调用,有返回值:' + data);
        return data
}

那我们该如何去调用呢?
直接在相应的地方如此调用即可:

NSString *returnStr = [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJS()"];
    NSLog(@"返回值为:%@",returnStr);

我们注意到这个地方会有个返回值,此时如果我们调用的JS代码有返回值得话,就会赋值到returnStr这个变量,可供我们后续使用。如果JS代码没有返回值得话,这个值默认是Undefine。
OBJC调用JS代码就是如此的简单。这个时候,有人说了,如果我们想向JS代码中传值该怎么弄呢?
JS代码:

function objcCallJSParam(param1,param2) {
        alert('来自objc的调用,带有参数:' + param1 + '  ' + param2);
}

OBJC代码:

- (void)callJSBtnParamAction:(id)sender {
    NSLog(@"开始调用JS函数,带有参数");
    [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJSParam('ljt','ths')"];
}

这样我们就可以向JS代码中传入参数了。

2、 JS调用OBJC

在这里我们使用拦截UIWebView的代码来实现,我们知道代理函数- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;会在UIwebView开始加载页面的时候调用,如果我们返回NO则这个页面什么也不做,如果返回YES则加载这个页面。有了这个特性,我们就可以来实现JS调用OBJC的代码。
</br>具体的流程: 页面响应时,重新设置页面的href,并将OBJC所用的参数以及所调用的指定函数,封装成一个特定格式的URL链接,然后我们在UIwebView的协议中拦截这个链接,然后解析出来,根据固定的格式做出相应的处理。
具体来说:当我们页面中一个事件,设置了页面的href,携带了两个参数,

//调用OBJC
function JSCallObjc() {
    window.location.href="www.baidu.com/param=ljt&ths";
}

我们在OBJC中可以这么做:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSLog(@"将要开始加载页面");
    NSString *urlStr = [[request URL] absoluteString];
    if ([urlStr containsString:@"param"]) {
        NSRange range = [urlStr rangeOfString:@"param="];
        NSString *paramStr = [urlStr substringFromIndex:range.location + range.length];
        NSLog(@"param=%@",paramStr);
        UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc弹框" message:paramStr preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
        [alterVC addAction:okAction];
        [self presentViewController:alterVC animated:YES completion:nil];
        return NO;
    }
    return YES;
}

这样就实现了JS调用OBJC的效果,当然这个例子不是那么好,href的格式我是随便写写,当真正使用起来的时候需要事先设计好合理的封装方式。
</br></br>

第三方库WebViewJavascriptBrige交互

这个部分我们使用有名的第三方库WebViewJavascriptBrige来介绍一下,关于这个库的实现,网上已经有很多介绍,关于原理的实现就不多介绍了,直接进入使用环节,使用这个库的时候有一个地方需要注意一下,在所加载的页面中需要实现固定的写法:

//固定函数,必须这样写
    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)
    }

    //所有交互的函数都写在这里面
    setupWebViewJavascriptBridge(function(bridge) {
        ····
    })
1、 OBJC调用JS

首先我们需要在JS中注册一个函数,供OBJC来调用

bridge.registerHandler('objcCallJS', function(data, responseCallback) {
            myAlert(data)
            var responseData = { 'Javascript Says':'Right back atcha!' }
            responseCallback(responseData)
})

这样注册之后,我们就可以在OBJC代码中调用这个名为objcCallJS的函数了。

OBJC中的代码:

- (void)callJSBtnAction:(id)sender {
    NSLog(@"调用JS函数");
    //callHandler有几种形式
    //- (void)callHandler:(NSString *)handlerName 只调用函数
    //- (void)callHandler:(NSString *)handlerName data:(id)data 调用的同时携带数据
    //- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback 不但调用和携带数据,而且设置回调函数处理所需的数据(如果需要处理结果数据)
//    [self.bridge callHandler:@"objcCallJS" data:@{@"key":@"value"} responseCallback:^(id responseData){
//        NSLog(@"%@",responseData);
//    }];
//    [self.bridge callHandler:@"objcCallJS"];
    [self.bridge callHandler:@"objcCallJS" data:@{@"key":@"value"}];
    
}

这个地方有几处我们需要说明的地方
1、objcCallJS这个名称必须是一样的
2、OBJC中调用JS的函数是通过- (void)callHandler:(NSString*)handlerName data:(id)data;这个借口调用的,这个接口的最终调用实现是- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;,当我们调用的时候,<strong>handlerName</strong>对应JS代码中注册的函数名,<strong>data</strong>对应JS代码中的data,即为OBJC向JS中传入的参数,还有一个有意思的参数就是<strong>responseCallback</strong>,就是JS代码的responseCallback,也就是说,当我们的JS代码处理完成之后,OBJC还有一次机会可以对JS代码处理完成的逻辑进行处理。当然data和responseCallback这两个参数都不是必须的。

2、 JS调用OBJC

与OBJC调用JS的逻辑类似,

首先我们需要在OBJC中注册JS能够调用的函数:

//注册js调用函数,并设定回调。js中可以调用JSCallObjc的函数
    [self.bridge registerHandler:@"JSCallObjc" handler:^(id data, WVJBResponseCallback responseCallback){
        NSString *paramStr = [data objectForKey:@"key"];
        UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc弹框" message:paramStr preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
        [alterVC addAction:okAction];
        [self presentViewController:alterVC animated:YES completion:nil];
        responseCallback(@"Response from testObjcCallback");
    }];

注册完成以后,我们就可以在JS代码中调用这个注册好的函数了:

//调用OBJC中的函数
        var callbackButton = document.getElementById('btn')
        callbackButton.onclick = function(e) {
            e.preventDefault()
                                 
            //callHandler的参数可变
            //bridge.callHandler('JSCallObjc') 没有携带数据
            //bridge.callHandler('JSCallObjc', {'key': 'value'}) 携带数据
            //bridge.callHandler('JSCallObjc', {'key': 'value'},function(response) {
            //      log('JS got response', response)
            //  }) 携带参数和回调函数
                   
            //bridge.callHandler('JSCallObjc')              
            bridge.callHandler('JSCallObjc',{'key': 'value'})
            
            
        }

我们可以看到,这个逻辑和接口与OBJC调用JS的逻辑和接口大同小异,具体的参数也是差不多的。
到此我们就可以利用这个第三方库来实现OBJC和JS的互相调用。其实这个库的内部还是通过拦截协议来实现这个交互过程的。

JavaScriptCore框架

JavaScriptCore框架是在iOS7之后引入的框架,这个框架在JS交互上为我们提供了很大帮助,可以在html界面上调用OC方法并传参,也可以在OC上调用JS方法并传参。关于JavaScriptCore的详细介绍和注意事项,请大家自行google之。这里我们只是简单介绍一下用法,很浅显,请大家不要见怪。
使用之前,我们需要导入JavaScriptCore.framework这个框架

context上下文,context上下文直接设置和调用
1、 OBJC调用JS

OBJC调用JS之前,需要获取一下JS的上下文,

self.context = [self.homeWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        NSLog(@"self.contex=%@",self.context);

获取到这个上下文之后,我们就可以对JS的执行环境做处理了,假如我们需要调用JS中已经实现过的函数,我们可以直接调用相应的函数就行:

- (void)callJSBtnAction:(id)sender {
    NSLog(@"开始调用JS函数,有返回值");
    //第一种
//    NSString *returnStr = [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJS()"];
//    NSLog(@"返回值为:%@",returnStr);
    
    //第二种
//    NSString *js = @"objcCallJS()";
//    JSValue *value = [self.context evaluateScript:js];
//    NSLog(@"%@",[value toString]);
    
    //第三种
    JSValue *valueFuc = self.context[@"objcCallJS"];
    JSValue *value = [valueFuc callWithArguments:nil];
    NSLog(@"%@",[value toString]);
}

以上代码中我们展示了三种不同的调用方式,特别说明的是第三种,首先我们从上下文中获取要执行的函数,并把它保存在一个JSValue变量中JSValue *valueFuc = self.context[@"objcCallJS"];,然后对这个变量调用其函数- (JSValue *)callWithArguments:(NSArray *)arguments;传入参数。
我们可以在JS中这样使用:
因为我们传入的参数为nil,所以我们在JS中没有获取参数,代码demo中我们提供了获取参数的版本,具体的可以参照代码demo,

function objcCallJS() {
        var data = 'ljt'
        alert('来自objc的调用,有返回值:' + data);
        return data
 }
2、 JS调用OBJC

首先我们在OBJC中,增加JS可以执行的代码,怎么添加呢?还是需要用到上面所提到的上下文context
加入我们需要实现一个可以接收参数的函数名为JSCallObjcParam,该怎么写呢?

//有参数
    __weak typeof(self) weakSelf = self;
    self.context[@"JSCallObjcParam"] = (id)^(NSString *param1, NSString *param2) {
        NSLog(@"有参,无返回值");
        NSString *message = [NSString stringWithFormat:@"%@:%@",param1,param2];
        UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc弹框" message:message preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
        [alterVC addAction:okAction];
        [weakSelf presentViewController:alterVC animated:YES completion:nil];
    };

这样我们就实现了一个可以在JS中调用的函数JSCallObjcParam,这个函数就收两个参数。
我们在JS中这样调用:

//调用OBJC
    function callObjcParam(param1,param2) {
        var data = JSCallObjcParam('ljt','ths')
        alert('来自objc(callObjcParam)的返回值:' + data);
    }

这么两步我们就实现了iOS和JS的相互调用。</br>
demo中我们分别实现了相互调用的传参和不传参,有返回值和没有返回值的情况,具体的可以看demo代码。

JSExport协议,通过JSExport协议设置和调用

JSExport是一个协议,我们可以自定义一个协议,继承此协议,在协议中声明可以在JS中使用的API函数。

首先我们实现一个这么样的协议ObjcJSDelegate

@protocol ObjcJSDelegate <JSExport>

//有参数
- (void)JSCallObjcParam:(NSString *)param1 with:(NSString *)param2;
//无参数
- (void)JSCallObjc;  
//有返回值
- (NSString *)JSCallObjcReturn;
@end

<strong>在这里我们需要知道的是,这个协议中所声明的接口是供JS调用的,也就是说JS代码调用OBJC的相关接口,可以放在这个协议中。而OBJC调用JS还是通过上下文来直接调用的</strong>

其次,我们需要一个实现了这个代理的类,我们就直接实现在@interface JSExportViewController ()<ObjcJSDelegate>UIWebView所在的ViewController中:

#pragma mark ObjcJSDelegate

- (void)JSCallObjcParam:(NSString *)param1 with:(NSString *)param2 {
    NSLog(@"有参,无返回值");
    NSString *message = [NSString stringWithFormat:@"%@:%@",param1,param2];
    UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc弹框" message:message preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
    [alterVC addAction:okAction];
    [self presentViewController:alterVC animated:YES completion:nil];
}

- (void)JSCallObjc {
    NSLog(@"无参,无返回值");
}

- (NSString *)JSCallObjcReturn {
    NSLog(@"无参,有返回值");
    return @"ljt";
}

然后我们需要让JS知道如何调用我们所需要的函数,这个时候同样需要前面一直提到的上下文:

self.context[@"OBJCFuc"] = self;

如此,我们就可以在JS中通过OBJCFuc(OBJCFuc.接口名字)来调用我们之前协议实现的相关接口了:
JS调用

//调用OBJC
    function callObjcParam(param1,param2) {
        var data = OBJCFuc.JSCallObjcParamWith('ljt','ths')
        alert('来自objc(callObjcParam)的返回值:' + data);
    }
    function callObjc() {
        var data = OBJCFuc.JSCallObjc()
        alert('来自objc(callObjc)的返回值:' + data);
    }
    
    function callObjcReturn() {
        var data = OBJCFuc.JSCallObjcReturn()
        alert('来自objc(callObjcReturn)的返回值:' + data);
    }

这样就实现了JS调用OJBC。


这篇文章,只是简单的介绍了iOS和JS的交互的几种方法,很粗浅,很简单,但也许很实用,最后附上代码demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,670评论 0 9
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,521评论 33 466
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,042评论 25 707
  • 把瑜伽拉伸动作校正了下,每天若有其事、学啥就要学出个样子,本着这颗心,开始吧 绝不拖延、这是最近潜移默化显现出来的...
    Molly喵小北阅读 218评论 0 3
  • 人生最大的修养是包容 人生最大的修养是包容。它既不是懦弱也不是忍让,而是察人之难,补人之短,扬人之长,谅人之过,而...
    xcy无名阅读 227评论 0 0