iOS 与 JavaScript 的交互

2017-06-13  本文已影响93人  ding0039

方案一:传统的交互

优点:不需要等待页面加载完才触发,当相应的代码被运行就能调用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商定的方法名要统一,两端要合作一下。
上一篇 下一篇

猜你喜欢

热点阅读