用途
在iOS开发中,我们经常会碰到这样的需求:在UIWebView中的一个链接,点了之后不是进下一个网页,而是进下一个UIViewController,或者让ObjC代码做点事情。这在资讯类的应用中很常见,比如网易新闻、腾讯新闻,以及我们公司的东方财富通中的资讯。
而在旧的iOS版本中,系统不提供在Javascript直接调用ObjC的方法。只能通过变换location等发起网络请求的方式,使得UIWebViewDelegate
中的- (BOOL)webView:shouldStartLoadWithRequest:navigationType:
感知到,进而做ObjC的处理。
然而这样做比较不优雅,所有的事情都围绕在URL请求上面,而不是方法调用上面,看上去不优雅。MHGJavascriptBridge的用意便是将URL请求等等封装起来,让Javascript和ObjC代码注重于方法调用本身上来。
使用方法
MHGJavascriptBridge由3个文件组成,MHGJavascriptBridge.h
, MHGJavascriptBridge.m
, MHGJavascriptBridge.js
。将这三个文件加入Xcode工程中。注意,MHGJavascriptBridge.js
必须加入到资源文件中(在”Building phases” -> “Copy bundle resources”中出现),Xcode默认会将.js文件加入到Compile Sources里面去,这是错误的。
Objective C设置
首先,我们需要初始化一个bridge,这通常是在一个UIViewController中进行的。这里假设在UIViewController中对bridge进行初始化。在初始化中,需要设定bridge的webView
属性:
@interface MHGWebViewController ()<UIWebViewDelegate>
@property (nonatomic, strong) MHGJavascriptBridge *bridge;
@property (nonatomic, strong) UIWebView *webView;
@end
...
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_bridge = [[MHGJavascriptBridge alloc]init];
_bridge.webView = self.webView;
}
return self;
}
在MHGJavascriptBridge中,所有能被Javascript调用的Objective C方法将以block的形式呈现。首先我们需要定义一些blocks,然后对每一个block起名。
- (void)viewDidLoad
{
[super viewDidLoad];
[self.bridge setBlockName:@"button1OnClick" block:^(NSDictionary *dict) {
// do stuff
NSLog(@"button1 on click with params:%@", dict);
}];
[self.bridge setBlockName:@"beginSomeTasks" block:^(NSDictionary *dict) {
// do stuff
NSLog(@"begin some tasks with params:%@", dict);
}];
...
}
MHGJavascriptBridge的原理是构造特定的URL,并且用UIWebViewDelegate
中的- (BOOL)webView:shouldStartLoadWithRequest:navigationType:
拦截这个URL。所以在这个delegate方法中,我们需要加入拦截语句:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
BOOL intercepted = [self.bridge interceptRequest:request]; //必须有
// Do other things ...
return YES;
}
其中,interceptRequest:
方法会返回一个BOOL
,如果拦截成功,则返回YES
。
这样,Objective C部分就设置完成了。其中需要注意的是,block被调用时,会传入一个dict
,这是Javascript部分代码调Objective C代码时所传的参数。
Javascript设置
Javascript部分设置比较简单,最基本的设置是要保证UIWebView中的HTML引入了MHGJavascriptBridge.js
:
<script src="MHGJavascriptBridge.js"></script>
Javascript调用Objective C代码
一旦设置完成之后,Javascript和Objective C就能互相调用了。代码如下:
var button1ClickEventHandler = function (){
// Do stuff ...
MHGJavascriptBridge.callNativeBlock('button1OnClick',{'url':imageURL});
};
<button onClick="button1ClickEventHandler()">button 1</button>
这时,点击button1时,就能触发Objective C的代码了。MHGJavascriptBridge.callNativeBlock
有两个参数,第一个参数是在Objective C中注册的block名字,第二个参数是传给block里面的dict
的额外信息。其中第二个参数必须是一个字典(或者说是一个Javascript Object),或者什么都不传。
Objective C调用Javascript代码
方法和上述类似:
...
[self.bridge callJavascriptFunction:@"setImageWithURL" withParams:@[fileURL.absoluteString]];
...
其中第一个参数是Javascript函数名。如果你在HTML中定义了function xxx(){}
或者var xxx = function(){}
的话,就能被调用。第二个参数是一个数组,传的是Javascript函数要用的参数列。
局限性
- Javascript调用Objective C时,所有的调用都是异步的,暂时无法实现同步调用。
- Javascript调用Objective C时,所传参数受URL长度限制而限制。