iOS之OC与JS交互(WebViewJavascriptBridge)

前言

最近项目开发中用到了OC与JS交互方面的知识,以前也用过UIWebView JS与OC交互方面的,使用的苹果在iOS7开放的javascriptCore框架,使用起来挺方便快捷,javascriptCore源码是开放的,有兴趣的可以去了解一下。

自从iOS8,苹果就UIWebView性能不好,推出了WKWebView,以及github上评分很高的WebViewJavascriptBridge里面最新版本也最WKWebView做了兼容。

实现的方式

所以我总结的方式,分UIWebview、WKWebView、以及通用版本的第三方WebViewJavascriptBridge,进行实现。

  • UIWebView中JS与OC交互
  • WKWebView中JS与OC交互(只能iOS8及之后的版本)
  • WebViewJavascriptBridge对UIWebView与WKWebView做了同意处理。

WebViewJavascriptBridge

优点

  • UIWebView和WKWebView使用基本相同

  • WebViewJavascriptBridge使用起来方便快捷

  • 安卓版本和iOS版本WebViewJavascriptBridge,实现的JS与原生交互都是一样的,不用针对两个端分别设置不同的代码

基本原理

  • WebViewJavascriptBridge就相当与沟通OC与JS的桥梁。

  • 把 OC 的方法注册到桥梁中,让 JS 去调用。

  • 把 JS 的方法注册在桥梁中,让 OC 去调用。

就是谁注册就是等着被调用,如下图:

15936146-2840fa7dce227602.png
WebViewJavascriptBridge原理图.png

使用步骤

  • 项目中导入 WebViewJavaScriptBridge 框架
pod ‘WebViewJavascriptBridge’

  • 导入头文件
#import <WebViewJavascriptBridge.h> 

  • 初始化WKWebView/UIWebView设置代理

    • 要是UIWebView那就设置UIWebViewDelegate

    • 要是WKWebView那就设置WKNavigationDelegate、WKUIDelegate

代码如下:

//初始化WKWebView
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
webView.navigationDelegate = self;
webView.UIDelegate = self;
[self.view addSubview:webView];

//或者初始化UIWebView
 UIWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
webView.delegate = self;
[self.view addSubview:webView];   
 
  • 给webView建立JS与OjbC的沟通桥梁
 self.bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
[self.bridge setWebViewDelegate:self];

  • OC 注册两个方法,等着JS调用,responseCallback()是返回js的数据,可不传

//registerHandler 注册,等着JS调用 responseCallback是返回js的数据
[self.bridge registerHandler:@"getUserIdFromObjC" handler:^(id data, WVJBResponseCallback responseCallback) {
        
    if (responseCallback) {
    
        responseCallback(@{@"userId":@"123456"});
   }
}];
    
//js调用的这个方法不需要给js反馈数据
[self.bridge registerHandler:@"notReturnFromJS" handler:^(id data, WVJBResponseCallback responseCallback) {
    
    NSLog(@"data -- %@",data);
}];

  • 在clik事件添加OnOCCallJSClick事件,调用JS
- (void) OnOCCallJSClick:(id)sender {
    
    //这种调用js方式,不需要js在给返回值
    [self.bridge callHandler:@"getInfoFromJS" data:@{@"name":@"张三"}];
    
    //callHandler 调用JS  这个方式是,调用js,js有返回值的话,responseData就是js返回的
    [self.bridge callHandler:@"getInfoFromJS" data:@{@"name":@"张三"} responseCallback:^(id responseData) {
        
        
    }];
}

  • 在你需要加载的html中在<script></script>写上统一的代码,这段代码就能使js与oc建立bridge。
 <script>
      window.onerror = function(err) {
        log('window.onerror: ' + err)
      }
    
      /*这段代码是固定的,必须要放到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)
      }
    
      /*与OC交互的所有JS方法都要放在此处注册,才能调用通过JS调用OC或者让OC调用这里的JS*/
      setupWebViewJavascriptBridge(function(bridge) {
       var uniqueId = 1
       function log(message, data) {
         var log = document.getElementById('log')
         var el = document.createElement('div')
         el.className = 'logLine'
         el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
         if (log.children.length) {
            log.insertBefore(el, log.children[0])
         } else {
           log.appendChild(el)
         }
       }
       
       /* Initialize your app here */
       
       /*我们在这注册一个js调用OC的方法,不带参数,
        *且不用ObjC端反馈结果给JS:打开本demo对应的博文
        */
       bridge.registerHandler('openWebviewBridgeArticle', function() {
                              
                              confirm("郭杨和小代是好朋友吗?");
          log("openWebviewBridgeArticle was called with by ObjC")
       })
       
      /*JS给ObjC提供公开的API,在ObjC端可以手动调用JS的这个API。
       *接收ObjC传过来的参数,且可以回调ObjC
       */
       bridge.registerHandler('getUserInfos', function(data, responseCallback) {
         log("Get user information from ObjC: ", data)
         responseCallback({'userId': '123456', 'blog': '标哥的技术博客'})
       })
                                   
      /*JS给ObjC提供公开的API,ObjC端通过注册,就可以在JS端调用此API时,得到回调。
       *ObjC端可以在处理完成后,反馈给JS,这样写就是在载入页面完成时就先调用
       */
    bridge.callHandler('getUserIdFromObjC', function(responseData) {
                    
         log("JS call ObjC's getUserIdFromObjC function, and js received response:", responseData)
       })
       
    })        
</script>
    
  • 在JS中也有registerHandler这个方法,注册方法等着OC来调用

  • 在JS中callHandler方法来调用OC方法,

  • JS的responseCallback是OC返回的数据,OC可以不传,responseCallback就为空

注意

如果使用WKWebview来实现WebViewJavascriptBridge,依然存在调用系统的confirm、alert、prompt弹出框被拦截,所以还是要实现UIDelegate代理的三个方法,拦截以后弹出原生的弹出框

#pragma mark - WKUIDelegate

//! alert(message)
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:message preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }];
    [alertController addAction:cancelAction];
    [self presentViewController:alertController animated:YES completion:nil];
}

//! confirm(message)
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler {
    
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Confirm" message:message preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }];
    UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }];
    [alertController addAction:cancelAction];
    [alertController addAction:confirmAction];
    [self presentViewController:alertController animated:YES completion:nil];
}

//! prompt(prompt, defaultText)
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler {
    
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:nil preferredStyle:UIAlertControllerStyleAlert];
    [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.placeholder = defaultText;
    }];
    UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(alertController.textFields[0].text);
    }];
    [alertController addAction:confirmAction];
    [self presentViewController:alertController animated:YES completion:nil];
}

结尾

WebViewJavascriptBridge使用起来比较方便快捷,并且还有安卓版本的WebViewJavascriptBridge,所有安卓和iOS都使用的话,就避免了前端既要写一份安卓又要写一份iOS的JS与OC交互,调试起来只要一端通了,JS那边应该就没啥问题,排查问题方便,不用的iOS和安卓会有不同的问题,可能你解决iOS会引起安卓问题,所以统一性比较重要。(如些有不足请批评指正,满意的话给个赞~~~)

猜你喜欢

转载自blog.csdn.net/weixin_34408717/article/details/90932309
今日推荐