WebViewJavascriptBridge的使用和封裝

iOS原生和UIWebView交互一般采用JavaScript來做,而目前第三方中框架比較好用,還是WebViewJavascriptBridge。這里基于WebViewJavascriptBridge(版本5.0.5)封裝了些代碼,作用是最后能實現(xiàn)Web傳參到原生進行原生頁面的跳轉(zhuǎn)或者方法的直接調(diào)用以及原生傳至給Web讓Web根據(jù)得到的值進行業(yè)務或者界面的編寫。

一.前期工作

1.1 下載庫

你需要下載WebViewJavascriptBridge第三方庫,點擊這里進入,測試工程是使用pod下載,版本為5.0.5

1.2 界面搭建

用到的主要文件如下:


主要使用文件.png

入口使用原始的ViewController,讓它帶了NavigationController,中間按鈕點擊后會push出BridgeVc,BridgeVc帶了一個UIWebview并加載網(wǎng)頁WebTest.html,該網(wǎng)頁利用了寫好的JavaScript文件WebBridge.js來和原生界面進行交互。WebTest.html中有3個按鈕,分別點擊交互后對應push或者present出BViewController,BViewController帶2個屬性name和sex,BViewController界面出現(xiàn)后會打印這兩個屬性值,3種方式跳轉(zhuǎn)后打印的屬性值不同。

主頁面.png

![Web.png](http://upload-images.jianshu.io/upload_images/1221039-62aee6dc707b9648.png?imageMogr2/auto-orient/strip%7CimageView2/2/
w/1240)

打印屬性.png

二.代碼

2.1 BridgeVc代碼

該控制器主要負責顯示加載UIWebview內(nèi)容并且進行交互

//
//  BridgeVc.m
//  NewWebTest
//
//  Created by Jeffrey on 2017/5/14.
//  Copyright ? 2017年 Jeffrey. All rights reserved.
//

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
#pragma ide diagnostic ignored "CannotResolve"

#import "BridgeVc.h"
#import "WebViewJavascriptBridge.h"

@interface BridgeVc ()
@property WebViewJavascriptBridge *bridge;
@property(nonatomic, strong) UIWebView *webView;

@end

@implementation BridgeVc

- (void)viewDidLoad {
    [super viewDidLoad];
    /**基礎設置,并初始化bridge*/
    [self baseConfig];
    /**配置WebBridge*/
    [self bridgeConfig];
    /**加載本地網(wǎng)頁*/
    [self loadExamplePage:self.webView];
    // Do any additional setup after loading the view.
}

/**常規(guī)設置,添加網(wǎng)頁*/
- (void)baseConfig {
    self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:self.webView];
}

/**配置WebBridge*/
- (void)bridgeConfig {
    /**初始化bridge*/
    self.bridge = [WebViewJavascriptBridge bridgeForWebView:(WVJB_WEBVIEW_TYPE *) self.webView];

    /***
     * 向Web發(fā)送數(shù)據(jù)data,并接收Web返回的數(shù)據(jù)responseData
     * data: 發(fā)送給Web的數(shù)據(jù)
     * responseData:Web接收到數(shù)據(jù)后執(zhí)行了callHandler,帶回來responseData數(shù)據(jù)
     */
    NSDictionary *customData = @{
            @"token": @"tokenValue",
            @"phoneType": @"iPhone7P"
    };
    [self.bridge callHandler:@"LocalToWeb" data:customData responseCallback:^(id responseData) {
        NSLog(@"發(fā)送給網(wǎng)頁的內(nèi)容為:%@,接收到網(wǎng)頁的內(nèi)容為:%@", customData, responseData);
    }];



    /***
     * 注冊handler,接收Web發(fā)送的數(shù)據(jù)data并回傳給Web本地的數(shù)據(jù)
     * data:Web發(fā)送過來的數(shù)據(jù)
     * responseCallback:是個block,可以帶數(shù)據(jù)執(zhí)行,例如responseCallback(dataForWeb),dataForWeb為id類型
     * 目測常規(guī)的數(shù)組字典字符串等都可以傳,Web在接受數(shù)據(jù)后可以進行一些自定義操作
     * 目的:注冊完這個方法后,可以實現(xiàn):Web里調(diào)用js的pushViewControllerWithParameters方法后,原生界面根據(jù)方法內(nèi)的具體參數(shù)進行界面跳轉(zhuǎn)
     * 具體見WebTest.html文件
     * 另外,可以實現(xiàn)Web里調(diào)用js的performMethod方法后,直接調(diào)用本網(wǎng)頁控制器內(nèi)寫好的方法,這里測試的為其繼承類BaseViewController的
     * presentBWithName:sex:方法。
     */
    [self.bridge registerHandler:@"WebToLocal" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"網(wǎng)頁傳過來的數(shù)據(jù)是%@", data);
        NSArray *p = data[@"parameters"];
        NSString *methodName = data[@"methodName"];
        /**push控制器并帶參數(shù)*/
        if ([methodName isEqualToString:@"push"]) {
            NSString *controllerName = data[@"controllerName"];
            NSDictionary *parameters = data[@"parameters"];
            Class controllerClass = NSClassFromString(controllerName);
            UIViewController *controller = (UIViewController *) [[controllerClass alloc] init];
            @try {
                /**Web里傳過來的參數(shù)必須和要push出去的控制器屬性一一對應,否則會拋出下面的異常*/
                [controller setValuesForKeysWithDictionary:parameters];
            }
            @catch (NSException *exception) {
                if ([exception.name isEqualToString:@"NSUnknownKeyException"]) {
                    /**屬性沒對應上,會拋出異常,并告知是哪個字段設置錯誤*/
                    NSLog(@"要push出的控制器沒有該屬性:%@,請在js上重新確認", exception.userInfo[@"NSUnknownUserInfoKey"]);
                    @throw exception;
                }
            }
            [self.navigationController pushViewController:controller animated:YES];
        } else {
            /**如果不是push方法,則執(zhí)行常規(guī)的方法名帶參數(shù)的方法*/
            [self performWithTarget:self MethodName:methodName withParameters:p];
        }
        /**回傳內(nèi)容*/
    }];
}

/**執(zhí)行單個方法*/
- (void)performWithTarget:(id)target MethodName:(NSString *)methodName withParameters:(NSArray *)parameters {
    SEL method = NSSelectorFromString(methodName);
    NSMethodSignature *methodSignature = [[target class] instanceMethodSignatureForSelector:method];
    if (!methodSignature) {
        NSLog(@"無此方法名");
        return;
    }

    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
    invocation.target = target;
    invocation.selector = method;
    if (parameters.count > methodSignature.numberOfArguments - 2) {
        NSLog(@"參數(shù)個數(shù)不匹配");
    } else {
        for (int i = 0; i < parameters.count; i++) {
            NSString *value = parameters[(NSUInteger) i];
            [invocation setArgument:&value atIndex:i + 2];
        }
        [invocation invoke];
    }
}

/**加載網(wǎng)頁*/
- (void)loadExamplePage:(UIWebView *)webView {
    NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"WebTest" ofType:@"html"];
    NSString *appHtml = [NSString stringWithContentsOfFile:htmlPath encoding:NSUTF8StringEncoding error:nil];
    NSURL *baseURL = [NSURL fileURLWithPath:htmlPath];
    [webView loadHTMLString:appHtml baseURL:baseURL];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

#pragma clang diagnostic pop

原則上如果網(wǎng)頁端寫好,項目中只需要加入上面對應的bridgeConfig方法即可,會自動根據(jù)Web里的JavaScript來進行調(diào)整或者方法的執(zhí)行。

2.2 JavaScript代碼(WebBridge.js文件)

/**
 * Created by jeffrey on 2017/5/15.
 */

/**
 * 原始方法,兩個注冊都需要調(diào)用這個方法才能使用
 * @param callback
 * @returns {*}
 */
function setupWebViewJavascriptBridge(callback) {

    if (window.WebViewJavascriptBridge) {
        return callback(WebViewJavascriptBridge);
    } else {

    }
    if (window.WVJBCallbacks) {
        return window.WVJBCallbacks.push(callback);
    }
    window.WVJBCallbacks = [callback];
    var WVJBIframe = document.createElement('iframe');
    WVJBIframe.style.display = 'none';
    /**注意,git上原來的readme文檔上下面的資源有問題,會導致交互不能正常記性,下面這個是正常的*/
    WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function () {
        document.documentElement.removeChild(WVJBIframe)
    }, 0)
}


/**
 * push方法,讓原生界面進行push跳轉(zhuǎn)
 * @param controllerName 原生界面控制器名稱
 * @param parameters 原生界面控制器的各個屬性值(有的界面需要傳值跳轉(zhuǎn)),為字典類型,且鍵值對必須和控制器內(nèi)屬性一一對應,否則原生會拋異常。
 */
function pushViewControllerWithParameters(controllerName, parameters) {
    setupWebViewJavascriptBridge(function (bridge) {
        /**這里接收原生控制器的數(shù)據(jù)*/
        bridge.registerHandler('JS Echo', function (data, responseCallback) {
            responseCallback(data)
        });
        let data = {'methodName': 'push', 'controllerName': controllerName, 'parameters': parameters};
        bridge.callHandler('WebToLocal', data, function responseCallback(responseData) {
            console.log("JS received response:", responseData);
            alert(responseData['good'])
        })
    })
}

/**
 * 執(zhí)行原生網(wǎng)頁所在控制器的方法
 * @param methodName 方法名
 * @param parameters 所帶參數(shù),為數(shù)組。參數(shù)將按照方法里的順序賦值
 */
function performMethod(methodName,parameters) {
    setupWebViewJavascriptBridge(function (bridge) {
        let data = {'methodName': methodName, 'parameters': parameters};
        bridge.callHandler('WebToLocal', data, function responseCallback(responseData) {
            alert(responseData['good'])
        })
    })
}


/**
 * 注冊接收原生數(shù)據(jù)
 * 原生界面直接傳數(shù)據(jù)過來,接收后可以根據(jù)接收的數(shù)據(jù)寫出項目中的網(wǎng)頁需要的邏輯
 */
function registerHandlerLocalToWeb() {
    window.onload = function () {
        /**注冊接收原生數(shù)據(jù)*/
        setupWebViewJavascriptBridge(function (bridge) {
            /**
             * data: 原生傳過來的數(shù)據(jù)
             * responseCallback:傳入數(shù)據(jù)執(zhí)行后原生會接收到,數(shù)據(jù)為responseData
             */

            bridge.registerHandler('LocalToWeb', function (data, responseCallback) {
                /**下面后臺在獲取相關數(shù)據(jù)后可以對網(wǎng)頁進行一些操作,并返回原生一些數(shù)據(jù)*/
                alert('token是' + data['token']);
                let responseData = {'key1': 'this is a test', 'key2': 'Web To Local'};
                responseCallback(responseData);
            });
        });
    };
}

2.3 網(wǎng)頁內(nèi)使用JavaScript文件(WebTest.html)
注意:如果自己重新寫,記得把上面的JavaScript文件添加到Copy Bundle Resources,否則不會在html里生效。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>測試</title>
    <script src="WebBridge.js"></script>
    <script>
        /**注冊接收原生發(fā)過來的數(shù)據(jù)*/
        registerHandlerLocalToWeb();
    </script>
</head>
<body>
<p>這是個測試</p>
<script>
    let parametersA = {'name': 'Jeff', 'sex': 'M'};
    let parametersB = {'name': 'Tom', 'sex': 'F'};
    let parametersC = ['Lucy','F'];
</script>
<button onclick="pushViewControllerWithParameters('BViewController', parametersA)">點我A</button>
<button onclick="pushViewControllerWithParameters('BViewController', parametersB)">點我B</button>
<button onclick="performMethod('presentBWithName:sex:', parametersC)">點我present</button>
</body>
</html>

這里可以看到,html中在點擊事件中執(zhí)行js的pushViewControllerWithParameters方法,并傳入iOS原生控制器名稱(類名稱)和該控制器的各屬性(按照字典形式傳入),即會push出對應控制器,這里還可以在push完后給出回調(diào)讓html使用具體的返回數(shù)據(jù)

點擊執(zhí)行的performMethod方法,需要傳入原生控制器的方法名字和參數(shù),例如上面的第三個按鈕,點擊后調(diào)用了BridgeVc的方法presentBWithName:sex:(父類方法也可以調(diào)用),傳入?yún)?shù)parametersC為數(shù)組,為方法里的各個參數(shù),順序從左到右。

另外在<head>標簽執(zhí)行的js:registerHandlerLocalToWeb()為注冊去收到原生頁面的傳至,具體傳至后如何使用可以在方法內(nèi)部定義或者另行根據(jù)項目封裝。

三.總結(jié)

最后根據(jù)項目的需求,先在原生中集成交互的代碼(bridgeConfig相關代碼,可以單獨摘出來作為工具類也行),然后把js文件適當修改,扔給后臺,在需要用到的網(wǎng)頁上直接調(diào)用對應的方法即可。也可以根據(jù)自己項目中的邏輯進行再修改來使用。****
本工程git地址:https://github.com/JeffreyWW/JFWebBridgeTest

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容