美文网首页iOS下JS与OC互相调用
iOS下 JS 与 OC 互相调用(六) - WKWebView

iOS下 JS 与 OC 互相调用(六) - WKWebView

作者: 路飞_Luck | 来源:发表于2018-08-18 11:47 被阅读173次

序言

WebViewJavascriptBridge 是一个有点年代的 JS 与 OC 交互的库,使用该库的应用不少,目前这个库有近12000 Star。最新的版本要兼容 WKWebView,所以新增了几个类。但是总体类文件个数不是很多,仔细阅读还是很容易理解的。

V6.0.3.png

上一篇文章介绍了UIWebView 如何通过WebViewJavascriptBridge 来实现JS 与OC 的互相调用,iOS下 JS 与 OC 互相调用(五) - UIWebView+WebViewJavascriptBridge,这一篇来介绍一下WKWebView 又是如何通过WKWebViewJavascriptBridge 来实现JS 与OC 的互相调用的。WKWebView 下使用WKWebViewJavascriptBridge与UIWebView 大同小异。主要是示例化的类不一样,一些与webView 相关的API调用不一样罢了。

WKWebViewJavascriptBridge使用讲解

先看效果图

JS_OC_WKWebViewJavascriptBridge.gif
第一步,使用Pods 将WebViewJavascriptBridge库添加到工程中。

如何使用 Pods将WebViewJavascriptBridge库添加到工程中省略,其 github 的地址是WebViewJavascriptBridge,我使用的是V6.0.3版本

第二步,创建WKWebView和WKWebViewJavascriptBridge示例
#import "WKBridgeViewController.h"
#import <WKWebViewJavascriptBridge.h>
#import <WebKit/WebKit.h>

@interface WKBridgeViewController()<WKNavigationDelegate, WKUIDelegate>

/** webView */
@property(nonatomic, strong)WKWebView *webView;
/** progress */
@property (strong, nonatomic)UIProgressView *progressView;
/** bridge */
@property(nonatomic, strong)WKWebViewJavascriptBridge *webViewBridge;

@end
  • 2.1 创建WKWebView
    这一步,唯一需要注意的地方,就是不用再设置WKWebViewnavigationDelegate
- (void)initWKWebView {
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    configuration.userContentController = [WKUserContentController new];
    
    WKPreferences *preferences = [WKPreferences new];
    preferences.javaScriptCanOpenWindowsAutomatically = YES;
    preferences.minimumFontSize = 30.0;
    configuration.preferences = preferences;
    
    self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
    [self.view addSubview:self.webView];
    
    NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"index.html" ofType:nil];
    NSString *localHtml = [NSString stringWithContentsOfFile:urlStr encoding:NSUTF8StringEncoding error:nil];
    NSURL *fileURL = [NSURL fileURLWithPath:urlStr];
    [self.webView loadHTMLString:localHtml baseURL:fileURL];
    
    self.webView.UIDelegate = self;
    [self.view addSubview:self.webView];
}
  • 2.2 创建WKWebViewJavascriptBridge
// WKWebViewJavascriptBridge
self.webViewBridge = [WKWebViewJavascriptBridge bridgeForWebView:self.webView];
[self.webViewBridge setWebViewDelegate:self];

上一步说了不用再设置WKWebViewnavigationDelegate,那是因为在{-bridgeForWebView:}内已经将WKWebViewnavigationDelegate设置为WKWebViewJavascriptBridge的实例了。

然后看看bridgeForWebView:方法是如何实现的

+ (instancetype)bridgeForWebView:(WKWebView*)webView {
    WKWebViewJavascriptBridge* bridge = [[self alloc] init];
    [bridge _setupInstance:webView];
    [bridge reset];
    return bridge;
}

- (void) _setupInstance:(WKWebView*)webView {
    _webView = webView;
    _webView.navigationDelegate = self;
    _base = [[WebViewJavascriptBridgeBase alloc] init];
    _base.delegate = self;
}
第三步,注册js 要调用的Native 功能。
- (void)registerNativeFunctions {
    [self registShareFunction];
    
    [self registLocationFunction];
    
    [self registPayFunction];
}

- (void)registShareFunction {
    [self.webViewBridge registerHandler:@"shareClick" handler:^(id data, WVJBResponseCallback responseCallback) {
        // data 的类型与 JS中传的参数有关
        NSDictionary *tempDic = data;
        // 在这里执行分享的操作
        NSString *title = [tempDic objectForKey:@"title"];
        NSString *content = [tempDic objectForKey:@"content"];
        NSString *url = [tempDic objectForKey:@"url"];
        NSLog(@"JS 传递给 OC 的参数:%@",[NSString stringWithFormat:@"分享成功:%@,%@,%@",title,content,url]);
        
        // 将分享的结果返回到JS中
        NSString *result = [NSString stringWithFormat:@"分享成功:%@,%@,%@",title,content,url];
        responseCallback(result);
    }];
}

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler ,我们可以这样理解,后面的block 参数是js 要调用的Native 实现,前面的handlerName 是这个Native 实现的别名。然后js 里调用handlerName 这个别名,WebViewJavascriptBridge最终会执行block 里的Native 实现。

为了便于维护,我们可以将JS要调用的Native方法都集中到一起,然后单个功能再封装一个方法。

第四步,完成 HTML 必要的 JS 代码

HTML 中有一个必须要添加的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)
}

添加完setupWebViewJavascriptBridge方法,需要在JS中主动调用一次该方法:

setupWebViewJavascriptBridge(function(bridge) {
     bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
        alert('JS方法被调用:'+data);
        responseCallback('js执行过了');
     })
})

主动调用setupWebViewJavascriptBridge有两个目的
1、执行一次wvjbscheme://__BRIDGE_LOADED__请求。
2、注册Native 要调用的js 功能。

执行wvjbscheme://__BRIDGE_LOADED__,然后在WKWebViewnavigationDelegate方法中拦截该URL ,然后往HMTL中注入js。以下源码都摘自WebViewJavascriptBridge

- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    if (webView != _webView) { return; }
    NSURL *url = navigationAction.request.URL;
    __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;

    if ([_base isCorrectProcotocolScheme:url]) {
        // 在这里拦截wvjbscheme://__BRIDGE_LOADED__
        if ([_base isBridgeLoadedURL:url]) {
            // 这里会注入js 
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) {
            [self WKFlushMessageQueue];
        } else {
            [_base logUnkownMessage:url];
        }
        decisionHandler(WKNavigationActionPolicyCancel);
    }
    
    if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
        [_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
    } else {
        decisionHandler(WKNavigationActionPolicyAllow);
    }
}

- (void)injectJavascriptFile {
    //读取js 内容
    NSString *js = WebViewJavascriptBridge_js();
    // 执行Native 的API,实现将js 注入 到HMTL中。
    [self _evaluateJavascript:js];
    if (self.startupMessageQueue) {
        NSArray* queue = self.startupMessageQueue;
        self.startupMessageQueue = nil;
        for (id queuedMessage in queue) {
            [self _dispatchMessage:queuedMessage];
        }
    }
}

WKWebView 执行js 的API 与 UIWebView 有些不同,WKWebView 用的是{-evaluateJavaScript: completionHandler:},这个API 不会立刻返回执行结果,js 的执行结果会在block 中返回。

第五步,调用 Native 功能

原理其实非常的简单,例如我想要利用Native 获取分享信息,那么在HTML中添加一个按钮,onclick事件是shareClick(),按照如下实现即可。
示例代码如下:

function shareClick() {
    var params = {'title':'测试分享的标题','content':'测试分享的内容','url':'http://www.baidu.com'};
    WebViewJavascriptBridge.callHandler('shareClick',params,function(response) {
         alert(response);
        document.getElementById("returnValue").value = response;
     });
}

Native 执行完代码,将获取到的分享信息,通过callHandler 的第三方参数,回调返回到js 中。
response 可以是单个值,也可以是数组、键值对等。
当然如果我们调用Native 的时候,没有参数或者不需要Native 返回信息到js 中。我们还可以这样写:

// 没有参数,有回调可以这样写
function locationClick() {
    WebViewJavascriptBridge.callHandler('locationClick',function(response) {
        alert(response);
        document.getElementById("returnValue").value = response;
    });
}

// 没有参数,又不需要回调可以这样写
function shake() {
    WebViewJavascriptBridge.callHandler('shakeClick');
}

至此,JS 通过WebViewJavascriptBridge调用Native 的功能就完成了。

第六步,Native 调用 JS 功能

Native 调用js 功能与 js 调用Native 的原理和流程一样。

  • 1、现在js 中注册,Native 要调用的功能。

  • 2、Native 调用注册时,该功能的别名,就可以完成调用。
    在js 中注册 Native 要调用的功能,同样需要为该功能设置一个别名HandlerName

  • 6.1 js 注册Native 要调用的功能

setupWebViewJavascriptBridge(function(bridge) {
     bridge.registerHandler('testJSFunction', function(data, responseCallback) {
        alert('JS方法被调用:'+data);
        responseCallback('js执行过了');
     })
    // 注册其他的功能
    //bridge.regsiterHandler.....
})

上述代码,是在JS 中注册了一个别名叫testJSFunction的js功能,第二个参数是一个function。function里的data ,就是Native 调用该功能时传过来的参数,responseCallback是执行完js 代码后,通过responseCallback将必要的信息返回到Native中。

  • 6.2 Native 调用js 里注册的功能
//    // 如果不需要参数,不需要回调,使用这个
//    [_webViewBridge callHandler:@"testJSFunction"];
//    // 如果需要参数,不需要回调,使用这个
//    [_webViewBridge callHandler:@"testJSFunction" data:@"一个字符串"];
    // 如果既需要参数,又需要回调,使用这个
    [_webViewBridge callHandler:@"testJSFunction" data:@"一个字符串" responseCallback:^(id responseData) {
        NSLog(@"调用完JS后的回调:%@",responseData);
    }];

到这里WKWebView 利用WKWebViewJavascriptBridge 实现Native 调用js 的功能就完成了。

注意:JS 有动态参数的特性,调用js 的方法,可以传0个参数,1个参数,N个参数都可以。

例如,我们在js中定义一个test()方法,我们可以调用test(),来执行这个方法;如果有参数要传进来,也可以调用test(xxx);如果有多个参数,那么就用test(xxx,xxx)。当然如果我们定义的参数是test(a,b,c),也可以少传参数,或者不传参数调用test()。


总结

利用WKWebViewJavascriptBridge来实现JS与OC的交互的优点:

  • 获取参数时,更方便一些,如果参数中有一些特殊符号或者url带参数,能够很好的解析。

也有一些缺点:

  • 做一次交互,需要执行的js 与原生的交互步骤较多,至少有两次。
  • 需要花较多的时间,理解WKWebViewJavascriptBridge的原理和使用步骤。

本文主要参考iOS下JS与OC互相调用(六)--WKWebView + WebViewJavascriptBridge,该作者整理的非常详细,说的也很到位,非常感谢该作者。


项目连接地址 - UIWeb_WebViewJavascriptBridge


更多 JS 与 OC 交互文章请看下面
iOS下 JS 与OC 互相调用(一) - UIWebView 拦截 URL
iOS下 JS 与OC 互相调用(二) - JavaScriptCore
iOS 下 JS 与 OC 互相调用(三) - WKWebView 拦截 URL
iOS下JS与OC互相调用(四)-MessageHandler
iOS下 JS 与 OC 互相调用(五) - UIWebView+WebViewJavascriptBridge


生活源于创造,技术源于分享,愿生活越来越美好。

相关文章

网友评论

    本文标题:iOS下 JS 与 OC 互相调用(六) - WKWebView

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