iOS 与 JavaScript 的交互


方案一:传统的交互

优点:不需要等待页面加载完才触发,当相应的代码被运行就能调用OC的方法(相比 JavaScriptCore而言)
缺点:需要繁琐地解释字符串得到相应的方法名和传值,且调用的方法也不能传递返回值;

1、OC调用JS

使用stringByEvaluatingJavaScriptFromString方法,可以将javascript嵌入页面中。不过需要等UIWebView中的页面加载完成之后去调用。
- (void)viewDidLoad{
    [super viewDidLoad];
    webview.backgroundColor = [UIColor clearColor]; 
    webview.scalesPageToFit =YES;
    webview.delegate =self;
    NSURL *url =[[NSURL alloc] initWithString:@"https://www.google.com.hk"];

    NSURLRequest *request =  [[NSURLRequest alloc] initWithURL:url];
    [webview loadRequest:request];   
}
//1、OC中调用JS的对象,在webViewDidFinishLoad方法中就可以通过javascript操作界面元素
 - (void)webViewDidFinishLoad:(UIWebView *)webView {  
    //1.1、获取当前页面的url
    NSString *currentURL = [webView stringByEvaluatingJavaScriptFromString:@"document.location.href"];
    //1.2、获取当前页面的title
   NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"]; 
    //1.3、修改界面元素的值(这样就实现了在google搜索关键字:“Terence”的功能。)
    NSString *js_result = [webView stringByEvaluatingJavaScriptFromString:@"document.getElementsByName('q')[0].value='Terence';"];
    //1.4、表单提交
    NSString *js_result2 = [webView stringByEvaluatingJavaScriptFromString:@"document.forms[0].submit(); "];
    //1.5、插入js代码(上面的功能我们可以封装到一个js函数中,将这个函数插入到页面上执行,代码如下:)
    // a、首先通过js创建一个script的标签,type为'text/javascript'。
    // b、然后在这个标签中插入一段字符串,这段字符串就是一个函数:myFunction,这个函数实现google自动搜索关键字的功能。
    // c、然后使用stringByEvaluatingJavaScriptFromString执行myFunction函数。
    [webView stringByEvaluatingJavaScriptFromString:
        @"var script = document.createElement('script');" 
         "script.type = 'text/javascript';" 
         "script.text = \"function myFunction() { " 
         "var field = document.getElementsByName('q')[0];" 
         "field.value='Terence';" 
         "document.forms[0].submit();" 
         "}\";" 
         "document.getElementsByTagName('head')[0].appendChild(script);"//添加到head标签
    ];
    [webView stringByEvaluatingJavaScriptFromString:@"myFunction();"];
    
    //2.1、调用JS的函数  
    [webView stringByEvaluatingJavaScriptFromString:@"clickme();"]; 
    //2、OC中调用JS的方法带参数
    NSString *value = @“把我显示出来”;
    [self.webView stringByEvaluatingJavaScriptFromString:@“transValue(‘%@‘)”,value];  
 }  
<html>  
<head>  
     <meta xmlns="http://www.w3.org/1999/xhtml" http-equiv="Content-Type" content="text/html; charset=utf-8" />  
     <title>这是一个html示例文件</title>  
     <script Type='text/javascript'>  
         function clickme() {  
             alert('点击按钮了!');  
         }  
         function transValue(insert) {  
             alert(insert);  
         }  
     </script>  
 </head>  
 <body>  
     <h1>OC与JS互动</h1>  
 </body>  
 </html>

2、JS调用OC

webView拦截url链接,获取自定义协议内容,再处理结果
//JS 函数
function sendToOC(){
    var para1 = document.getElementById("element1").value;
    var para2 = document.getElementById("element2").value;
    //  "gallery://"为自定义协议头;para1&para2为要传给OC的值,以","作为分隔
    var url = "gallery://"+para1+","+para2;
    document.loc ation = url;
}     
//遵守UIWebViewDelegate代理协议。
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
    //拿到网页的实时url
    NSString *requestStr = [[request.URL absoluteString] stringByRemovingPercentEncoding];
    //在url中寻找自定义协议头"gallery://"
    if ([requestStr hasPrefix:@"gallery://"]) {
        // 以"://"为中心将url分割成两部分,放进数组arr
        NSArray *arr = [requestStr componentsSeparatedByString:@"://"];
        //取其后半段的参数值
        NSString *paramStr = arr[1];
        //以","为标识将后半段url分割成若干部分,放进数组
        NSArray *paraArray = [paramStr componentsSeparatedByString:@","];

        //取出参数,进行使用
        if (paraArray.count) {
            NSLog(@"有参数");
            [self doSomeThingWithParamA:paraArray[0] andParamB:paraArray[1]];
        }else{
            NSLog(@"无参数");
        }
        return NO;
    }

    return YES;
}
//对JS传来的值进行处理
- (void)doSomeThingWithParamA:(id)paramA andParamB:(id)paramB{
    NSLog(@"para1:%@--para2:%@", paramA, paramB);
}

方案二:JavaScriptCore.framework框架

优点:苹果官方框架,简单,高效;
缺点:OC调用JS,必须等HTML加载完成之后才可以

1、OC调用JS

 //网页加载完成调用此方法  
-(void)webViewDidFinishLoad:(UIWebView *)webView  
{  
    //首先创建JSContext 对象(此处通过当前webView的键获取到jscontext)  
    JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];  
    //准备执行的js代码
    NSString *alertJS=@"alert('test')";   
    //通过oc方法调用js的alert(在webview加载结束,注入一段js代码)  
    [context evaluateScript:alertJS];
} 

2、JS调用OC

#import <JavaScriptCore/JavaScriptCore.h>

-(void)webViewDidFinishLoad:(UIWebView *)webView{
    // 1.创建实例
    JSContext *jsContext = [webView     valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    // 2.关联异常
    jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
        context.exception = exceptionValue;
        NSLog(@"异常信息:%@", exceptionValue);
    };
    // 3.获取参数(sendFunc,sendParam是在JS定好的方法名)
    //遍历参数
    jsContext[@"sendFunc"] = ^{
        NSArray *args = [JSContext currentArguments];
        for (id obj in args){
            NSLog("parameter:%@",obj);
        }
    }
    //或者直接传入参数
    jsContext[@"sendParam"] = ^(NSDictionary *dic){
        NSLog("params:%@",dic);
    }
}
//其中jsendToOC是指js的函数名,得到args数组里面为js函数的参数,即js要传给oc的参数。

js 代码:

function onClick(){
    var para1 = document.getElementById("element1").value;
    var para2 = document.getElementById("element2").value;    
            
    sendFunc(para1,para2);
}
//第二种方法
<input type="button" value="测试" onclick="sendParam({'paramKey':'paramValue'})" />
在需要传值给OC的函数里,例如上面的click函数,直接调用sendToOC函数即可。

方案三:WebViewJavascriptBridge第三方框架(github地址

WebViewJavascriptBridge同时支持UIWeView和WKWebView,无论是JS调用OC还是OC调用JS,都可以正常传值和返回值;而且在页面加载时只要JS代码被运行就可以进行交互,不会遇到上面两种方案的缺点,所以是现在处理交互的主流做法。
<!-- 申明交互 -->
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)
}

<!-- 处理交互  方法名要和ios内定义的对应-->
setupWebViewJavascriptBridge(function (bridge){
    //OC传值给JS 'wevViewJSHandler'为双方自定义好的统一方法名;'data'是OC传过来的值;'responseCallback'是JS接收到之后给OC的回调
    <!--处理 oc 调用 js -->
    bridge.registerHandler('webViewJSHandler', function(data, responseCallback) {
        //打印OC传过来的值
        log('ObjC called wevViewJSHandler with', data)
        var responseData = { 'Javascript Says':'Right back atcha!' }
        log('JS responding with', responseData)
        //处理完,回调传值给oc
        responseCallback(responseData)
    })
    
    var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
        callbackButton.innerHTML = '点击我,我会调用oc的方法'
        callbackButton.onclick = function(e) {
            e.preventDefault()                                 
            <!--处理 js 调用 oc -->
            bridge.callHandler('loginAction', {'userId':'zhangsan','name': '章三'}, function(response) {
                 //处理oc过来的回调
                 alert('收到oc过来的回调:'+response)
            })
        }
    })
}
#immport "WebViewJavascriptBridge.h"
-(void)viewDidLoad{
    [super viewDidLoad];
    //1.初始化  WebViewJavascriptBridge
    if (_bridge) { return; }
    [WebViewJavascriptBridge enableLogging];    //设置第三方Bridge是否可用
    _bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
    [_bridge setWebViewDelegate:self];    //设置bridge代理
    
    //请求加载html,注意:这里h5加载完,会自动执行一个调用oc的方法
    [self loadExamplePage:webView];    
    
    //申明js调用oc方法的处理事件,这里写了后,h5那边只要请求了,oc内部就会响应
    [self JS2OC];    
  
    //模拟操作:2秒后,oc会调用js的方法
    //注意:这里厉害的是,我们不需要等待html加载完成,就能处理oc的请求事件;此外,webview的request 也可以在这个请求后面执行(可以把上面的[self loadExamplePage:webView]放到[self OC2JS]后面执行,结果是一样的)
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self OC2JS];        
    });
}
//JS 调用 OC
-(void)JS2OC{
    /*
     含义:JS调用OC
     @param registerHandler 要注册的事件名称(比如这里我们为loginAction)
     @param handel 回调block函数 当后台触发这个事件的时候会执行block里面的代码
     */
    [_bridge registerHandler:@"loginAction" handler:^(id data, WVJBResponseCallback responseCallback) {
        // data js页面传过来的参数  假设这里是用户名和姓名,字典格式
        NSLog(@"JS调用OC,并传值过来");
        
        // 利用data参数处理自己的逻辑
        NSDictionary *dict = (NSDictionary *)data;
        NSString *str = [NSString stringWithFormat:@"用户名:%@  姓名:%@",dict[@"userId"],dict[@"name"]];
        [self renderButtons:str];
        
        // responseCallback 给js的回复
        responseCallback(@"报告,oc已收到js的请求");
    }];
}

//OC 调用 JS
-(void)OC2JS{
    /*
     含义:OC调用JS
     @param callHandler 商定的事件名称,用来调用网页里面相应的事件实现
     @param data id类型,相当于我们函数中的参数,向网页传递函数执行需要的参数
     注意,这里callHandler分3种,根据需不需要传参数和需不需要后台返回执行结果来决定用哪个
     */
    
    //[_bridge callHandler:@"registerAction" data:@"我是oc请求js的参数"];
    [_bridge callHandler:@"registerAction" data:@"uid:123 pwd:123" responseCallback:^(id responseData) {
        NSLog(@"oc请求js后接受的回调结果:%@",responseData);
    }];
}
关键点就是:OC和JS商定的方法名要统一,两端要合作一下。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容