项目中经常会遇到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>