iOS进阶7--桥接JS和Native

简景焕
2023-12-01

项目中经常会遇到web和原生之间的交互,在没有使用桥接的情况下,我们主要是在webView代理方法里面处理,如下:

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
    NSString* newURL = [[request URL] absoluteString];
    //判断是否是内部定义URL,内部定义以protoclo://开头
    if (![newURL hasPrefix:@"protoclo://"]) {
        return YES;
    }
    //转换格式
    NSString* newParam = [newURL stringByRemovingPercentEncoding];
    NSString* jsJson = [newParam stringByReplacingOccurrencesOfString:@"protoclo://" withString:@""];
    if (jsJson.length == 0) {
        return YES;
    }
    //解析json串
    self.webJson = [[BDWebJson alloc] initWithString:jsJson error:nil];
    if (self.webJson == nil) {
        return YES;
    }
    //判断action
    if ([self.webJson.action isEqualToString:@"redirect"]) {
        //跳转
    } else if ([self.webJson.action isEqualToString:@"call-native-app-api"]) {
        //调用原生API
    }
    return NO;
}

上面方法适用于那些js简单调用native,如果两边需要大量数据交互,一直在这个方法里显得臃肿,于是找来第三方法桥接工具,WebViewJavascriptBridge,它是支持UIWebView和MKWebView,github地址如下:
https://github.com/marcuswestin/WebViewJavascriptBridge

接下来,我们直接看它使用方法

#import "ExampleUIWebViewController.h"
#import "WebViewJavascriptBridge.h"

@interface ExampleUIWebViewController ()
@property WebViewJavascriptBridge* bridge;
@end

@implementation ExampleUIWebViewController

- (void)viewWillAppear:(BOOL)animated {
    if (_bridge) { return; }

    //1.初始化一个webview
    UIWebView* webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:webView];

    //2.将此webview与WebViewJavascriptBridge关联

    [WebViewJavascriptBridge enableLogging];

    _bridge = [WebViewJavascriptBridge bridgeForWebView:webView webViewDelegate:self handler:^(id data, WVJBResponseCallback responseCallback) {
        //data是js那边传过来的参数
        NSLog(@"ObjC received message from JS: %@", data);

        //WVJBResponseCallback responseCallback 是将反应结果返回给js
        responseCallback(@"Response for message from ObjC");
    }];

    //ps:此时你的webview就与js搭上桥了。下面就是方法的互调和参数的互传。



    //(1) js调oc方法(两端方法testObjcCallback要定义统一才能接收到通讯,可以通过data给oc方法传值,使用responseCallback将值再返回给js)
    [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"testObjcCallback called: %@", data);
        responseCallback(@"Response from testObjcCallback");
    }];
    //这里注意testObjcCallback这个方法的标示。html那边的命名要跟ios这边相同,才能调到这个方法。当然这个名字可以两边商量着自定义。简单明确即可。

    //(2)oc调js方法(通过data可以传值,通过 response可以接受js那边的返回值 )
    //注意这里的 testJavascriptHandler也是个方法标示。
    [_bridge send:@"A string sent from ObjC before Webview has loaded." responseCallback:^(id responseData) {
        NSLog(@"objc got response! %@", responseData);
    }];

    //(3)oc给js传值(通过 response接受返回值 )
    [_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }];

    [self renderButtons:webView];
    [self loadExamplePage:webView];

    //(4)oc给js传值(无返回值)
    [_bridge send:@"A string sent from ObjC after Webview has loaded."];
}

- (void)webViewDidStartLoad:(UIWebView *)webView {
    NSLog(@"webViewDidStartLoad");
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    NSLog(@"webViewDidFinishLoad");
}

- (void)renderButtons:(UIWebView*)webView {
    UIFont* font = [UIFont fontWithName:@"HelveticaNeue" size:12.0];

    UIButton *messageButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [messageButton setTitle:@"Send message" forState:UIControlStateNormal];
    [messageButton addTarget:self action:@selector(sendMessage:) forControlEvents:UIControlEventTouchUpInside];
    [self.view insertSubview:messageButton aboveSubview:webView];
    messageButton.frame = CGRectMake(10, 414, 100, 35);
    messageButton.titleLabel.font = font;
    messageButton.backgroundColor = [UIColor colorWithWhite:1 alpha:0.75];

    UIButton *callbackButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [callbackButton setTitle:@"Call handler" forState:UIControlStateNormal];
    [callbackButton addTarget:self action:@selector(callHandler:) forControlEvents:UIControlEventTouchUpInside];
    [self.view insertSubview:callbackButton aboveSubview:webView];
    callbackButton.frame = CGRectMake(110, 414, 100, 35);
    callbackButton.titleLabel.font = font;

    UIButton* reloadButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [reloadButton setTitle:@"Reload webview" forState:UIControlStateNormal];
    [reloadButton addTarget:webView action:@selector(reload) forControlEvents:UIControlEventTouchUpInside];
    [self.view insertSubview:reloadButton aboveSubview:webView];
    reloadButton.frame = CGRectMake(210, 414, 100, 35);
    reloadButton.titleLabel.font = font;
}

- (void)sendMessage:(id)sender {
    [_bridge send:@"A string sent from ObjC to JS" responseCallback:^(id response) {
        NSLog(@"sendMessage got response: %@", response);
    }];
}

- (void)callHandler:(id)sender {
    id data = @{ @"greetingFromObjC": @"Hi there, JS!" };
    [_bridge callHandler:@"testJavascriptHandler" data:data responseCallback:^(id response) {
        NSLog(@"testJavascriptHandler responded: %@", response);
    }];
}

- (void)loadExamplePage:(UIWebView*)webView {
    NSString* htmlPath = [[NSBundle mainBundle] pathForResource:@"ExampleApp" ofType:@"html"];
    NSString* appHtml = [NSString stringWithContentsOfFile:htmlPath encoding:NSUTF8StringEncoding error:nil];
    NSURL *baseURL = [NSURL fileURLWithPath:htmlPath];
    [webView loadHTMLString:appHtml baseURL:baseURL];
}
@end

js代码如下:

<script>
    window.onerror = function(err) {
        log('window.onerror: ' + err)
    }

    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 = 'https://__bridge_loaded__';
        document.documentElement.appendChild(WVJBIframe);
        setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
    }

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

        bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
            log('ObjC called testJavascriptHandler with', data)
            var responseData = { 'Javascript Says':'Right back atcha!' }
            log('JS responding with', responseData)
            responseCallback(responseData)
        })

        document.body.appendChild(document.createElement('br'))

        var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
        callbackButton.innerHTML = 'Fire testObjcCallback'
        callbackButton.onclick = function(e) {
            e.preventDefault()
            log('JS calling handler "testObjcCallback"')
            bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
                log('JS got response', response)
            })
        }
    })
    </script>
 类似资料: