WebView与JS的几种交互

作者: 谈Xx | 来源:发表于2016-03-18 14:08 被阅读20720次

    最近整理了一下原生与H5之间的交互方式,简单的做个总结。
    OC端与JS的交互,大致有这几种:拦截协议、JavaScriptCore库、WKWebView、自定义NSURLProtocol拦截、WebViewJavascriptBridge。

    1. JavaScriptCore一个iOS7引进的标准库,iOS7以前也有开发者自行导入使用。Web端也比较容易统一。
    2. WebViewJavascriptBridge是一个第三方库,其原理还是使用了对web view的请求拦截,支持WKWebview。封装了完整的OC与JS相互调用的方法,不过需要Web端配合编写相应的方法。安卓方面听说有一个同名的库,如果不是统一使用的话,需要Web端写2套JS的话,那就有点蛋疼了。
    3. WKWebView,iOS8加入的WebKit。相对于UIWebView,具有更强大的功能。提供一个WKScriptMessageHandler,可以实现JS对WebView的调用。
    4. 协议的拦截,比较常用的一种方式。
    5. 在自定义NSURLProtocol中,拦截请求,也可以实现相应的方法调用。

    1. JavaScriptCore


    JavaScriptCore中类及协议:

    • JSContext:给JavaScript提供运行的上下文环境,通过-evaluateScript:方法就可以执行一JS代码
    • JSValue:封装了JS与ObjC中的对应的类型,以及调用JS的API等
    • JSManagedValue:管理数据和方法的类
    • JSVirtualMachine:处理线程相关,使用较少
    • JSExport:这是一个协议,如果采用协议的方法交互,自己定义的协议必须遵守此协议,在协议中声明的API都会在JS中暴露出来,才能调用

    对于JSContext和JSValue的更多使用方式可以看下这篇,介绍的比较完整ios7 JavaScriptCore.framework 本文主要简单总结下交互相关内容。

    ObjC调用JS

    在JavaScriptCore中提供的调用JS的方法
    - (JSValue *)evaluateScript:(NSString *)script;方法就可以执行一段JavaScript脚本,并且如果其中有方法、变量等信息都会被存储在其中以便在需要的时候使用。
    JSValue提供了- (JSValue *)callWithArguments:(NSArray *)arguments;方法可以反过来将参数传进去来调用方法

     // 一个JSContext对象,就类似于Js中的window,
     // 只需要创建一次即可。
    JSContext *context = [[JSContext alloc] init];
    // 执行一段js
    [context evaluateScript:@"function add(a, b) { return a + b; }"];
    // 根据下标取出方法
    JSValue *add = context[@"add"];
    NSLog(@"Func: %@", add);
    // 传入参数 调用取到的方法
    JSValue *sum = [add callWithArguments:@[@(7), @(21)]];
    NSLog(@"Sum: %d",[sum toInt32]);
    //OutPut:
    // Func: function add(a, b) { return a + b; }
    // Sum: 28
    

    再来个栗子

     self.jsContext = [[JSContext alloc] init];
    
     [self.jsContext evaluateScript:@"var num = 10"];
     [self.jsContext evaluateScript:@"var squareFunc = function(value) { return value * 2 }"];
     // 调用 计算面积
     JSValue *square = [self.jsContext evaluateScript:@"squareFunc(num)"];
    
     // 可以通过下标的方式获取到方法
     JSValue *squareFunc = self.jsContext[@"squareFunc"];
     // 传入参数 调用取到的方法
     JSValue *value = [squareFunc callWithArguments:@[@"20"]];
     NSLog(@"%@", square.toNumber);
     NSLog(@"%@", value.toNumber); 
    

    JS调用OC

    使用JavaScriptCore进行原生与js的交互主要是2种方式,block注入模型使用协议代理

    Block方式

    JSContext *context = [[JSContext alloc] init];
    // 定义一个block
    context[@"log"] = ^() {
      NSLog(@"+++++++Begin Log+++++++");  
                   
      NSArray *args = [JSContext currentArguments];
      for (JSValue *jsVal in args) {
       NSLog(@"%@", jsVal);
      }
      
      JSValue *this = [JSContext currentThis];
      NSLog(@"this: %@",this);
      NSLog(@"-------End Log-------");
    };
    // 调用js执行log方法
     [context evaluateScript:@"log('ider', [7, 21],     
     { hello:'world', js:100 });"];
    

    当web端调用log方法,传入相关参数,就能调用OC端的block。实现交互。

    通过注入模型的方式交互

    • OC端要做的事情

    首先,我们自定义一个协议,而且这个协议必须要遵守JSExport协议
    协议暴露的方法,是供JS调用的方法。还可以实现回调

    @protocol JavaScriptObjectiveCDelegate <JSExport>
    
    // JS调用此方法来调用OC的share
    - (void)share:(NSDictionary *)params ;
    
    // JS调用此方法来调用OC的相机
    - (void)callCamera ;
    
    // 在JS中调用时,多个参数需要使用驼峰方式
    // 这里是多个个参数的。
    - (void)showAlert:(NSString *)title msg:(NSString *)msg;
    
    // 通过JSON传过来
    - (void)callWithDict:(NSDictionary *)params;
    // JS调用Oc,然后在OC中通过调用JS方法来传值给JS。
    - (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params;
    
    @end
    

    接下来,我们还需要定义一个模型:

    // 此模型用于注入JS的模型,这样就可以通过模型来调用方法。
    @interface HYBJsObjCModel : NSObject <JavaScriptObjectiveCDelegate>
    
    @property (nonatomic, weak) JSContext *jsContext;
    @property (nonatomic, weak) UIWebView *webView;
    
    @end
    

    模型的实现:

    @implementation HYBJsObjCModel
    
    - (void)share:(NSDictionary *)params {
     NSLog(@"Js调用了OC的share方法,参数为:%@", params);
    }
    
    - (void)callWithDict:(NSDictionary *)params {
     NSLog(@"Js调用了OC的方法,参数为:%@", params);
    }
    
    // JS调用了callCamera
    - (void)callCamera {
     NSLog(@"JS调用了OC的方法,调起系统相册");
    
     // JS调用后OC后,可以传一个回调方法的参数,进行回调JS
     JSValue *jsFunc = self.jsContext[@"jsFunc"];
     [jsFunc callWithArguments:nil];
    }
    
    - (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params {
     NSLog(@"jsCallObjcAndObjcCallJsWithDict was called, params is %@", params);
    
     // 调用JS的方法
     JSValue *jsParamFunc = self.jsContext[@"jsParamFunc"];
     [jsParamFunc callWithArguments:@[@{@"age": @10, @"name": @"lili", @"height": @158}]];
    }
    
    // 指定参数的用法
    // 在JS中调用时,函数名应该为showAlertMsg(arg1, arg2)
    - (void)showAlert:(NSString *)title msg:(NSString *)msg {
     dispatch_async(dispatch_get_main_queue(), ^{
       UIAlertView *a = [[UIAlertView alloc] initWithTitle:title message:msg delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
       [a show];
     });
    }
    @end
    

    JavaScriptCore使用注意

    JavaScript调用本地方法是在子线程中执行的,这里要根据实际情况考虑线程之间的切换。

    模型实现完了,在哪里注入呢。在controller的webView加载完成后
    我们是通过webView的valueForKeyPath获取的,其路径为documentView.webView.mainFrame.javaScriptContext
    这样就可以获取到JS的context,然后为这个context注入我们的模型对象。

    #pragma mark - UIWebViewDelegate
    - (void)webViewDidFinishLoad:(UIWebView *)webView 
    {
     self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
      // 通过模型调用方法,这种方式更好些。
      HYBJsObjCModel *model  = [[HYBJsObjCModel alloc] init];
      // 模型
      self.jsContext[@"OCModel"] = model;
      model.jsContext = self.jsContext;
      model.webView = self.webView;
      // 增加异常的处理
      self.jsContext.exceptionHandler = ^(JSContext *context,   
     JSValue *exceptionValue) {
        context.exception = exceptionValue;
        NSLog(@"异常信息:%@", exceptionValue);
     };
    }
    

    另外关于模型,也可根据需求直接将模型作为controller,去实现相关的方法实现,省去模型这一层。 如下:

    #pragma mark - UIWebViewDelegate
    - (void)webViewDidFinishLoad:(UIWebView *)webView 
    {
      self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; 
      self.jsContext[@"OCModel"] = self; 
      self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
      context.exception = exceptionValue; 
      NSLog(@"异常信息:%@", exceptionValue);
     };
    }
    
    
    • WEB端

    代码内容

    <!DOCTYPE html>
    <html>
    <head lang="en">
         <meta charset="UTF-8">
    </head>
    <body>
         <div style="margin-top: 100px"> 
            <h1>Objective-C和JavaScript交互的那些事</h1> 
            <input type="button" value="CallCamera" onclick="OCModel.callCamera()"> 
          </div>
    
           <div>
             <input type="button" value="Share" onclick="callShare()"> 
           </div>
    <script>
           var callShare = function() { 
              OCModel.share({'title': '标题', 'desc': '内容', 'shareUrl': 'http://www.jianshu.com/p/f896d73c670a');
           }
    
    </script>
    </body>
    </html>
    

    注意下 如果调用的方法是多个参数的,必须使用驼峰写法并去掉冒号

    - (void)showAlert:(NSString *)title msg:(NSString *)msg;
    需要这样调用
     OCModel. showAlertMsg('title','msg');
    

    以上就是简单的利用JavaScriptCore framework进行JS交互的用法,
    感谢
    iOS与JS交互实战篇(ObjC)
    Objective-C与JavaScript交互的那些事
    提供的资料参考,仅仅是做个总结

    2. WebViewJavascriptBridge

    这个第三方库起先是在UIWebView与JS的深度交互大神文中知悉。其还是使用拦截WebView请求方法,但是做了完整的封装后,使用起来还是很简单的。

    1) 导入

    #import "WKWebViewJavascriptBridge.h"
    

    2) 初始化

    self.bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
    // 开启日志,方便调试
    [WebViewJavascriptBridge enableLogging];
    

    3) Web端setupWebViewJavascriptBridge

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

    4)call setupWebViewJavascriptBridge

    setupWebViewJavascriptBridge(function(bridge) {
    
    /* Initialize your app here */
    
    bridge.registerHandler('JS Echo', function(data, responseCallback) {
        console.log("JS Echo called with:", data)
        responseCallback(data)
    })
    bridge.callHandler('ObjC Echo', function responseCallback(responseData) {
        console.log("JS received response:", responseData)
    })
    })
    

    • ObjC API

    OC端初始化时 默认消息处理器

    实例化WebViewJavascriptBridge并定义native端的默认消息处理器。

    JS调用bridge.send()即可触发默认处理。

       _bridge = [WebViewJavascriptBridge bridgeForWebView:webView handler:^(id data, WVJBResponse *response) {  
           NSLog(@"ObjC received message from JS: %@", data);  
           UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"ObjC got message from Javascript:" message:data delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];  
           [alert show];  
       }];
    

    OC端调用[self.bridge send];即可触发JS端的默认处理。

        [self.bridge send:@"Give me a response, will you?" responseCallback:^(id responseData) {
        NSLog(@"ObjC got its response! %@", responseData);
        }];
    

    OC端registerHandler接收JS调用

    在JS中调用了bridge.callHandler('getScreenHeight')就会触发OC注册的对应的handler,responseCallback中回调JS传递参数

        [self.bridge registerHandler:@"getScreenHeight" handler:^(id data, WVJBResponseCallback responseCallback) {
          NSLog(@"ObjC Echo called with: %@", data);
          responseCallback([NSNumber numberWithInt:[UIScreen              
          mainScreen].bounds.size.height]);
        }];
    

    或者 JS传递data给OC,OC打印

        [self.bridge registerHandler:@"log" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"Log: %@", data);
        }];
    

    OC端callHandler调用JS

    调用JS showAlert,传递data

    [self.bridge callHandler:@"showAlert" data:@"Hi from ObjC to JS!"];
    

    调用JS getCurrentPageUrl,在block中获取参数

    [self.bridge callHandler:@"getCurrentPageUrl" data:nil responseCallback:^(id responseData) {
        NSLog(@"Current UIWebView page URL is: %@", responseData);
    }];
    
    还可设置代理监听
    [bridge setWebViewDelegate:(UIWebViewDelegate*)webViewDelegate];
    
    • Javascript API

    JS registerHandler接收OC调用

    注册handle,OC可以通过[bridge callHandler:"handlerName" data:@"Foo"][bridge callHandler:"handlerName" data:@"Foo" responseCallback:^(id responseData) { ... }]进行调用JS

    OC传递data进行调用

    bridge.registerHandler("showAlert", function(data) { alert(data) })
    

    参数结果回传给OC

    bridge.registerHandler("getCurrentPageUrl", function(data, responseCallback) {
    responseCallback(document.location.toString())
    })
    bridge.callHandler("handlerName", data)
    

    JS 调用OC

    JS调用bridge.callHandler("handlerName", data)bridge.callHandler("handlerName", data, function responseCallback(responseData) { ... })

    调用OC端打印

    bridge.callHandler("Log", "Foo")
    

    调用OC端获取高度,在block中使用

    bridge.callHandler("getScreenHeight", null, function(response) {
    alert('Screen height:' + response)
    })
    

    3.WKWebView - iOS8 or Later

    iOS8,苹果新推出了WebKit,用WKWebView代替UIWebView和WebView。相关的使用和特性可以细读。
    WKWeb​View
    iOS 8 WebKit框架概览(下)
    WKWebView特性及使用

    • WKWebView新特性

      性能、稳定性、功能大幅度提升
      允许JavaScript的Nitro库加载并使用(UIWebView中限制)
      支持了更多的HTML5特性
      高达60fps的滚动刷新率以及内置手势
      GPU硬件加速
      KVO
      重构UIWebView成14类与3个协议,查看官方文档

    需要注意的是WKWebView貌似不支持NSURLProtocol和NSURLCache。不能做缓存的话,就蛋疼了。

    关于WKWebView的代理方法 这篇有比较完整的介绍
    http://www.jianshu.com/p/1d7a8525ad16

    下面是相关的交互方法


    • app调js方法

    WKWebView调用js方法和UIWebView类似,一个是evaluateJavaScript,一个是stringByEvaluatingJavaScriptFromString。获取返回值的方式不同,WKWebView用的是回叫函数获取返回值

     //直接调用js
        webView.evaluateJavaScript("hi()", completionHandler: nil)
    //调用js带参数
        webView.evaluateJavaScript("hello('liuyanwei')", completionHandler: nil)
    // 调用js获取返回值
       webView.evaluateJavaScript("getName()") { (any,error) -> Void in
            NSLog("%@", any as! String)
        }
    
    • js调app方法

    UIwebView没有js调app的方法,而在WKWebView中有了改进。具体步骤分为app注册handler,app处理handler委托,js调用三个步骤

    1. 注册handler需要在webView初始化之前,如示例,注册了一个webViewApp的handler
            config = WKWebViewConfiguration()
             //注册js方法
            config.userContentController.addScriptMessageHandler(self, name: "webViewApp")
             // 初始化
            webView = WKWebView(frame: self.webWrap.frame, configuration: config)
    
    1. 处理handler委托。ViewController实现WKScriptMessageHandler委托的func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage)代理方法。在里面处理事件。
            //实现WKScriptMessageHandler委托
            class ViewController:WKScriptMessageHandler
            //实现js调用ios的handle委托
            func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
            //接受传过来的消息从而决定app调用的方法
             let dict = message.body as! Dictionary<String,String>
             let method:String = dict["method"]!
             let param1:String = dict["param1"]!
             if method=="hello"{
                 hello(param1)
               }
             }
    
    1. js调用 。通过window.webkit.messageHandlers.webViewApp找到之前注册的handler对象,然后调用postMessage方法把数据传到app,app通过上一步的方法解析方法名和参数。webViewApp是之前注册的name

       var message = {
                       'method' : 'hello',
                       'param1' : 'liuyanwei',
                       };
       window.webkit.messageHandlers.webViewApp.postMessage(message);        
      

    如果需要app对js的调用有所响应,可以通过回叫函数的方式回应js。可以在调用app的时候增加一个js回叫函数名 ,app在处理完之后可以呼叫回叫函数并把需要的参数通过回叫函数的方式进行传递

    • 使用用户脚本来注入 JavaScript

    WKUserScript 允许在正文加载之前或之后注入到页面中。这个强大的功能允许在页面中以安全且唯一的方式操作网页内容。

    一个简单的例子如下,用户改变背景的用户脚本被插入到网页中:

    let source = "document.body.style.background = \"#777\";"
    let userScript = WKUserScript(source: source,   injectionTime: .AtDocumentEnd, forMainFrameOnly: true)
    
    let userContentController = WKUserContentController()
    userContentController.addUserScript(userScript)
    
    let configuration = WKWebViewConfiguration()
    configuration.userContentController = userContentController
    self.webView = WKWebView(frame: self.view.bounds, configuration: configuration)
    

    WKUserScript 对象可以以 JavaScript 源码形式初始化,初始化时还可以传入是在加载之前还是结束时注入,以及脚本影响的是这个布局还是仅主要布局。于是用户脚本被加入到 WKUserContentController 中,并且以 WKWebViewConfiguration 属性传入到 WKWebView 的初始化过程中。

    4. 拦截协议

    最简单也是最容易想到的一种
    UIWebView的代理方法,web view发出请求后拦截,查看是否为约定的协议,采取处理。

    -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
    {
        NSString *url = request.URL.absoluteString;
        if ([url rangeOfString:@"camera://"].location != NSNotFound) {
            // url的协议头是camera
            NSLog(@"callCamera");
            return NO;
        }
        return YES;
    }
    

    WKWebView

    -(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
    {
        NSString *url = navigationAction.request.URL.absoluteString;
        NSLog(@"%@",url);
        
        if (navigationAction.navigationType == WKNavigationTypeLinkActivated  && [url rangeOfString:@"camera://"].location != NSNotFound)
        {
            // url的协议头是camera
            NSLog(@"callCamera");
            decisionHandler(WKNavigationActionPolicyCancel);
            
            // dosomthing。。。
        }
        else
        {
            decisionHandler(WKNavigationActionPolicyAllow);
        }
       
    }
    

    5. NSURLProtocol拦截

    这种方式也是最近才看到,原本利用自定义NSURLProtocol来做缓存处理。相关的文章可以看:
    NSURLProtocol和NSRunLoop的那些坑
    iOS中的 NSURLProtocol
    在自定义的Protocol的- (void)startLoading方法中,可以拦截到请求。一般会在这里做缓存的判断与读取处理。在此处,也可以判断约定的协议,然后发送通知,客户端就可以接收到通知,执行相应的方法。

    - (void)startLoading
    {
        NSString * url = [[[self request] URL] absoluteString];
        
        if([url hasPrefix:@"LocalActions/"])
        {
            NSString * actname = [url stringByReplacingOccurrencesOfString:@"LocalActions/" withString:@"LocalAction_"];
      
            // 发送通知  客户端就可执行方法
            [[NSNotificationCenter defaultCenter] postNotificationName:actname object:nil];
        }
    }
    

    需要注意的是WKWebView貌似不支持NSURLProtocol和NSURLCache。不能做缓存的话,就蛋疼了。

    相关参考
    iOS与JS交互实战篇(ObjC版)
    Objective-C与JavaScript交互的那些事

    相关文章

      网友评论

      • JerseyBro:有心了。
        谈Xx:@泽西岛上咖啡 :relieved:
      • UncleFool:你好,请教一个问题。js端在调用window.webkit.messageHandlers.AppModel.postMessage()时,需要iOS端向js端传递一个值。请问上面这个方法可以接收返回值吗,iOS端应该怎么处理呢?
      • UncleFool:你好,请教一个问题。js端在调用window.webkit.messageHandlers.AppModel.postMessage()时,需要iOS端向js端传递一个值。请问上面这个方法可以接收返回值吗,iOS端应该怎么处理呢?
        谈Xx:使用postMessage的话 是在代理方法中处理具体的handle。所有想要回调js的话,可以主动调用js来告知结果,这个接口中不像JavaScriptCore,可以直接传递一个func作为jsvalue
      • sea_biscute:并不是WKWebview不支持NSURLProtocol,而是WK是一个单独的进程,所以和当前的APP并不共享同一个NSURLProtocol
        谈Xx:@sea_biscute 是的,这个当初后来有分享提到可以使用私有api,继续注册使用自定义的NSURLProtocol,当时写的草率没有注意
      • 306a480fd470:请问valueForKeyPath是固定的吗,还是跟后台有关?
        谈Xx:@芝欧 多个参数还是单个参数
        306a480fd470:@谈Xx 嗯 ,我现在遇到了个问题,就是当JS携带参数的时候 ,方法就进不去了 ,携带的参数去掉就可以进去 ,不知道改用什么方法解决
        谈Xx:@芝欧 固定的
      • 清都:请问在WKWebView下也用self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];吗?我把webView换成WKWebView,- (void)webViewDidFinishLoad:(UIWebView *)webView换成-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation,结果在self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];这里崩了,是不是WKWebView写法不一样?
      • CoderSJun:楼主 有一个问题 oc端调前端的一个loginResult的方法 前端在这个方法里面进行异步请求得到数据后,再通过window.webkit.messageHandlers.Login.postMessage();这种方式给我。可是我这边就是收不到 请问哪里有问题吗?还是这个postmsg的方法一定要及时返回的?我让前端做了2秒延迟在post我这边也收不到。
        一般请求是放在oc端还是js端啊?
        谈Xx:@CoderSJun 是咋了
        CoderSJun:@谈Xx 问题解决了 是我写的有点问题
        谈Xx:我不是很清楚 js是怎么回调客户端的?如果不在异步里直接调用,客户端能收到吗?就是确认是否和异步有关。 关于请求的话,我这边前端发和客户端发都使用过。前端发的话也会经过客户端走一步加密,也可以统一让客户端发,看似安全性高一点,也能解决一些跨域问题吧。
      • 飞同小可:为什么我在js 中写的初始化方法,在IOS的UIwebView中不会执行呢。
        我现在只能把onload方法里面的内容重新封装一个方法onReady,然后,在webview webViewDidFinishLoad的时候通过stringByEvaluatingJavaScriptFromString 去调用onReady方法。
        我还遇到一个问题,就是在方法里面,如果js里面不调用oc,然后向后台请求数据,请求的数据返回有问题,但是如果我在js里面调用oc方法(我就加了一个NSLog方法)。然后我在JS调用Ajax方法之前,调用一下log方法,请求数据就正确了。
        我只是加了个log而已,其他什么都没改,感觉好奇怪!
        谈Xx:第一个关于页面生命周期调用的我不是很清楚。后面你说正常的ajax返回有问题,我的想法是从返回的数据看看能不能看出什么原因,感觉跟调用OC的方法没有什么明显的关联。
      • ZhHS:请问wkwebview与h5交互,h5只能存在本地吗?我在本地尝试交互成功,挂到服务器,JS编译都通不过,提示undefined is not an object (evaluating 'window.webkit.messageHandlers'),请问这是什么原因呢?
        谈Xx:wk的交互应该很多人都有使用了,有时间可以把demo发我看看。tanjf@belink.com
      • da27c260cc85:documentView.webView.mainFrame.javaScriptContext 通过这种方式获得js上下文, 然后注入的对象, 最后怎么释放掉呢?
        da27c260cc85:@谈Xx 如果注册的对象是self呢? 有没有办法呢?
        谈Xx:@ArthurChi 我调试看了下引用的情况,是没有循环引用的持有问题的。并且包含webview的VC和model都会顺利的调用dealloc方法。我把demo上传了, 可以看下https://github.com/NNope/WebViewJavascript
        谈Xx:@ArthurChi 这个之前没注意到,有空看下引用的问题,和调试下对象,看看释放情况
      • 楼上那只猫:web中代码有问题,函数名字没对应,所以导致js无法调用OC,博主你这样随意,有点不负责哦.
        谈Xx:@楼上那只猫 web中所调用的2个方法,现在改对应了。OC端当时只是想多展现几个参数的使用情况,所以列出了好几个方法。确实会搞得一头雾水
        谈Xx:@楼上那只猫 啊,是的,之前有条评论提到了。 我没修改么。。。
      • 海浪萌物:通过注入模型的方式交互,按照上面的方法,代理方法我在viewController里面都实现了,weak关键字也改成strong了,但是点击页面JS就是调用不了OC,这是怎么回事呢
        海浪萌物:@海浪萌物 然后html里面:
        <div style="margin-top: 100px">
        <h1>Objective-C和JavaScript交互的那些事</h1>
        <input type="button" value="CallCamera" onclick="OCModel.callCamera()">
        </div>

        <div>
        <input type="button" value="Share" onclick="callShare()">
        </div>
        <script>
        var callShare = function() {
        var shareInfo = JSON.stringify({"title": "标题", "desc": "内容", "shareUrl": "http://www.jianshu.com/p/f896d73c670a&quot;,
        "shareIco":"http://upload-images.jianshu.io/upload_images/1192353-fd26211d54aea8a9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot;});
        OCModel.share(shareInfo);
        }

        var picCallback = function(photos) {
        showAlertMsg('haha','haha');
        }

        var shareCallback = function(){
        alert('成功');
        }
        </script>
        海浪萌物:@谈Xx这些都是viewController里面的:
        @property (strong, nonatomic) JSContext *jsContext;

        -(void)callWithDict:(NSDictionary *)params{

        NSLog(@"JS调用了OC的方法,参数是:%@",params);
        }

        -(void)callSystemCamera{

        NSLog(@"JS调用了OC的方法,调起了系统相册");
        //JS调用后OC,可以传一个回调方法的参数,进行回调JS
        JSValue *jsFunc = self.jsContext[@"jsFunc"];
        [jsFunc callWithArguments:nil];
        }

        -(void)jsCallObjectAndObjcCallJsWithDict:(NSDictionary *)params{

        NSLog(@"jsCallObjectAndObjcCallJsWithDict");
        //调用JS的方法
        JSValue *jsParamFunc = self.jsContext[@"jsParamFunC"];
        [jsParamFunc callWithArguments:@[@{@"age":@10,@"name":@"lili",@"height":@170}]];

        }

        //指定参数的用法,在JS中调用时,函数名应该为showAlert
        // 指定参数的用法
        // 在JS中调用时,函数名应该为showAlertMsg(arg1, arg2)
        - (void)showAlert:(NSString *)title msg:(NSString *)msg {

        dispatch_async(dispatch_get_main_queue(), ^{
        UIAlertView *a = [[UIAlertView alloc] initWithTitle:title message:msg delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
        [a show];
        });
        }


        -(void)callCamera{

        NSLog(@"callCamera");
        }
        -(void)callShare{

        NSLog(@"callShare");
        }
        /*
        JavaScriptCore使用注意
        JavaScript调用本地方法是在子线程中执行的,这里要根据实际情况考虑线程之间的切换。

        模型实现完了,在哪里注入呢。在controller的webView加载完成后
        我们是通过webView的valueForKeyPath获取的,其路径为documentView.webView.mainFrame.javaScriptContext。
        这样就可以获取到JS的context,然后为这个context注入我们的模型对象。
        */
        #pragma mark - UIWebViewDelegate
        - (void)webViewDidFinishLoad:(UIWebView *)webView
        {
        self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

        NSLog(@"self.jsContext:%@",self.jsContext);
        self.jsContext[@"OCModel"] = self;
        self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
        context.exception = exceptionValue;
        NSLog(@"异常信息:%@", exceptionValue);
        };
        }
        谈Xx:@海浪萌物 这个主要步骤是,一个协议里注册方法 一个模型里实现方法 一个上下文模型的设置 一个js的正确调用 你看下这几个点排查下
      • 41372c7b2bd0:如果需要app对js的调用有所响应,可以通过回叫函数的方式回应js。可以在调用app的时候增加一个js回叫函数名 ,app在处理完之后可以呼叫回叫函数并把需要的参数通过回叫函数的方式进行传递
        这一句可以实现 把app端的数据传递给js端吗?有没有具体的代码
        谈Xx:@龙_ 就是js在调用native的时候 多传一个js函数名,当native做完后,主动调用这个js

      本文标题:WebView与JS的几种交互

      本文链接:https://www.haomeiwen.com/subject/wyvbkttx.html