iOS原生和UIWebView交互一般采用JavaScript来做,而目前第三方中框架比较好用,还是WebViewJavascriptBridge。这里基于WebViewJavascriptBridge(版本5.0.5)封装了些代码,作用是最后能实现Web传参到原生进行原生页面的跳转或者方法的直接调用以及原生传至给Web让Web根据得到的值进行业务或者界面的编写。
一.前期工作
1.1 下载库
你需要下载WebViewJavascriptBridge第三方库,点击这里进入,测试工程是使用pod下载,版本为5.0.5
1.2 界面搭建
用到的主要文件如下:
入口使用原始的ViewController,让它带了NavigationController,中间按钮点击后会push出BridgeVc,BridgeVc带了一个UIWebview并加载网页WebTest.html,该网页利用了写好的JavaScript文件WebBridge.js来和原生界面进行交互。WebTest.html中有3个按钮,分别点击交互后对应push或者present出BViewController,BViewController带2个属性name和sex,BViewController界面出现后会打印这两个属性值,3种方式跳转后打印的属性值不同。
![Web.png]( http://upload-images.jianshu.io/upload_images/1221039-62aee6dc707b9648.png?imageMogr2/auto-orient/strip%7CimageView2/2/
w/1240)
二.代码
2.1 BridgeVc代码
该控制器主要负责显示加载UIWebview内容并且进行交互
//
// 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];
/**加载本地网页*/
[self loadExamplePage:self.webView];
// Do any additional setup after loading the view.
}
/**常规设置,添加网页*/
- (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发送数据data,并接收Web返回的数据responseData
* data: 发送给Web的数据
* responseData:Web接收到数据后执行了callHandler,带回来responseData数据
*/
NSDictionary *customData = @{
@"token": @"tokenValue",
@"phoneType": @"iPhone7P"
};
[self.bridge callHandler:@"LocalToWeb" data:customData responseCallback:^(id responseData) {
NSLog(@"发送给网页的内容为:%@,接收到网页的内容为:%@", customData, responseData);
}];
/***
* 注册handler,接收Web发送的数据data并回传给Web本地的数据
* data:Web发送过来的数据
* responseCallback:是个block,可以带数据执行,例如responseCallback(dataForWeb),dataForWeb为id类型
* 目测常规的数组字典字符串等都可以传,Web在接受数据后可以进行一些自定义操作
* 目的:注册完这个方法后,可以实现:Web里调用js的pushViewControllerWithParameters方法后,原生界面根据方法内的具体参数进行界面跳转
* 具体见WebTest.html文件
* 另外,可以实现Web里调用js的performMethod方法后,直接调用本网页控制器内写好的方法,这里测试的为其继承类BaseViewController的
* presentBWithName:sex:方法。
*/
[self.bridge registerHandler:@"WebToLocal" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"网页传过来的数据是%@", data);
NSArray *p = data[@"parameters"];
NSString *methodName = data[@"methodName"];
/**push控制器并带参数*/
if ([methodName isEqualToString:@"push"]) {
NSString *controllerName = data[@"controllerName"];
NSDictionary *parameters = data[@"parameters"];
Class controllerClass = NSClassFromString(controllerName);
UIViewController *controller = (UIViewController *) [[controllerClass alloc] init];
@try {
/**Web里传过来的参数必须和要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方法,则执行常规的方法名带参数的方法*/
[self performWithTarget:self MethodName:methodName withParameters:p];
}
/**回传内容*/
}];
}
/**执行单个方法*/
- (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(@"参数个数不匹配");
} else {
for (int i = 0; i < parameters.count; i++) {
NSString *value = parameters[(NSUInteger) i];
[invocation setArgument:&value atIndex:i + 2];
}
[invocation invoke];
}
}
/**加载网页*/
- (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
原则上如果网页端写好,项目中只需要加入上面对应的bridgeConfig
方法即可,会自动根据Web里的JavaScript来进行调整或者方法的执行。
2.2 JavaScript代码(WebBridge.js文件)
/**
* Created by jeffrey on 2017/5/15.
*/
/**
* 原始方法,两个注册都需要调用这个方法才能使用
* @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跳转
* @param controllerName 原生界面控制器名称
* @param parameters 原生界面控制器的各个属性值(有的界面需要传值跳转),为字典类型,且键值对必须和控制器内属性一一对应,否则原生会抛异常。
*/
function pushViewControllerWithParameters(controllerName, parameters) {
setupWebViewJavascriptBridge(function (bridge) {
/**这里接收原生控制器的数据*/
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'])
})
})
}
/**
* 执行原生网页所在控制器的方法
* @param methodName 方法名
* @param parameters 所带参数,为数组。参数将按照方法里的顺序赋值
*/
function performMethod(methodName,parameters) {
setupWebViewJavascriptBridge(function (bridge) {
let data = {'methodName': methodName, 'parameters': parameters};
bridge.callHandler('WebToLocal', data, function responseCallback(responseData) {
alert(responseData['good'])
})
})
}
/**
* 注册接收原生数据
* 原生界面直接传数据过来,接收后可以根据接收的数据写出项目中的网页需要的逻辑
*/
function registerHandlerLocalToWeb() {
window.onload = function () {
/**注册接收原生数据*/
setupWebViewJavascriptBridge(function (bridge) {
/**
* data: 原生传过来的数据
* responseCallback:传入数据执行后原生会接收到,数据为responseData
*/
bridge.registerHandler('LocalToWeb', function (data, responseCallback) {
/**下面后台在获取相关数据后可以对网页进行一些操作,并返回原生一些数据*/
alert('token是' + data['token']);
let responseData = {'key1': 'this is a test', 'key2': 'Web To Local'};
responseCallback(responseData);
});
});
};
}
2.3 网页内使用JavaScript文件(WebTest.html)
注意:如果自己重新写,记得把上面的JavaScript文件添加到Copy Bundle Resources
,否则不会在html里生效。
测试
这是个测试
这里可以看到,html中在点击事件中执行js的pushViewControllerWithParameters
方法,并传入iOS原生控制器名称(类名称)和该控制器的各属性(按照字典形式传入),即会push出对应控制器,这里还可以在push完后给出回调让html使用具体的返回数据
点击执行的performMethod
方法,需要传入原生控制器的方法名字和参数,例如上面的第三个按钮,点击后调用了BridgeVc的方法presentBWithName:sex:
(父类方法也可以调用),传入参数parametersC为数组,为方法里的各个参数,顺序从左到右。
另外在标签执行的js:
registerHandlerLocalToWeb()
为注册去收到原生页面的传至,具体传至后如何使用可以在方法内部定义或者另行根据项目封装。
三.总结
最后根据项目的需求,先在原生中集成交互的代码(bridgeConfig
相关代码,可以单独摘出来作为工具类也行),然后把js文件适当修改,扔给后台,在需要用到的网页上直接调用对应的方法即可。也可以根据自己项目中的逻辑进行再修改来使用。****
本工程git地址:https://github.com/JeffreyWW/JFWebBridgeTest