之前有记录过从零收拾一个hybrid框架–选择JS通信方案,在之前公司的项目里也搭建过JS交互的容器,不过是基于WKWebView的。而这篇文章主要是介绍UIWebView相关的,在这里也单独整理一下。
1.UIWebView的基本用法
其实UIWebView
用法比较简单(功能基本能满足需求),简单的创建并且调用即可
1 | - (void)loadRequest:(NSURLRequest *)request; |
如果需要监听页面加载的结果,或者需要判断是否允许打开某个URL,那需要设置UIWebView
的delegate
,代理只需要遵循``协议,并且在代理中实现下面的这些可选方法就可以:
1 | __TVOS_PROHIBITED @protocol UIWebViewDelegate <NSObject> |
2. UIWebView中js和oc的交互
2.1 原生调用js
UIWebView中原生调用js,一般我们是通过evaluatingJavaScript
直接注入执行JS代码。
实现步骤:
- 通过
JavaScriptCore
获取js上下文JSContext
- 然后通过
JSContext
的evaluateScript:
方法来获取返回值。因为该方法得到的是一个JSValue
对象,所以支持JavaScript的Array、Number、String、对象等数据类型。
1 | - (void)webViewDidFinishLoad:(UIWebView *)webView { |
有个问题:
如果我们原生调用了不存在的js方法,比如document.nullFunction
,那肯定会报错,怎么知道呢?
可以通过@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);
,设置该block来获取异常。
1 | - (void)webViewDidFinishLoad:(UIWebView *)webView { |
这样就可以捕获到异常错误了。
2.2 js调用原生
2.2.1 假跳转的请求拦截的方式
在Objective-C中,只要遵循了UIWebViewDelegate
协议,那么每次打开一个链接之前,都会触发如下方法:
1 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; |
在该方法中,捕获该链接,并且返回NO(阻止本次跳转),从而执行对应的OC方法。
1 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType |
这种方式优缺点也很明显:
- 优点:动态性比较强,可以适配h5页面的多种情况
- 缺点:js这边无法获取本次交互的返回值,不关心回调情况。
不过现在网上有的第三方JSBridge交互的框架,解决了这种无法回调的情况。比如WebViewJavascriptBridge
,DSBridge
等。
他们的主要思路是采用了将一个名为function
的callback
作为参数,通过一些封装,传递到OC(js->oc 传递参数和callbackId),然后在OC端执行完毕,再通过block
来回调callback(oc->js,传递返回值参数),实现异步获取返回值,比如在js端调用
WebViewJavascriptBridge
1
2
3
4
5//JS调用OC的分享方法(当然需要OC提前注册)share为方法名,shareData为参数,后面的为回调function
WebViewJavascriptBridge.callHandler('share', shareData, function(response) {
//OC端通过block回调分享成功或者失败的结果
alert(response);
});DSBridge
1
2
3zqtbridge.call("device.notification.alert", {"msg":"测试","title":"测试Alert"}, function (response) {
alert(response);
});我们项目里采用的就是DSBridge这种。
2.2.2 通过JavaScriptCore
注入(替换)的方式
还是通过JavaScriptCore
直接拿到整个WebView当前所拥有的JS上下文
拿到了JSContext,一切的使用方式就和直接操作JavaScriptCore没啥区别了,我们可以把任何遵循JSExport协议的对象直接注入JS,让JS能够直接控制和操作
1 | - (void)webViewDidFinishLoad:(UIWebView *)webView { |
通过上面的方法可以拿到当前WebView的JS上下文JSContext,然后就要准备往这个JSContext里面注入准备好的block,而这个准备好的block,负责解读JS传过来的数据,从而分发调用各种native函数指令。
js这边调用代码如下:
1 | //准备要传给native的数据,包括指令,数据,回调等 |
这里说的注入,其实可以理解为替换,context[@"yourMethodName"] = your block;
这样写不仅可以在有yourMethodName
方法时替换该JS方法为OC实现,还会在该方法没有时,添加方法。简而言之,有则替换,无则添加。
这种注入替换的方式,如何获取返回值呢?
同步返回,直接return
js中:
1
2
3
4
5
6
7//该方法传入两个整数,求和,并返回结果
function testAddMethod(a, b) {
//需要OC实现a+b,并返回
return a + b;
}
//js调用
console.log(testAddMethod(1, 5)); //output 5原生调用:
1
2
3
4Context[@"testAddMethod"] = ^NSInteger(NSInteger a, NSInteger b) {
return a * b;
};直接将方法结果返回给js
异步返回
思路其实也是通过原生得到结果后,通过原生的block执行js中的callBack。
场景:h5中有一个分享按钮,用户点击之后,调用native分享(微信分享、微博分享等),在native分享成功或者失败时,回调h5页面,告诉其分享结果,h5页面刷新对应的UI,显示分享成功或者失败。
在js中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//声明
function share(shareData) {
var title = shareData.title;
var imgUrl = shareData.imgUrl;
var link = shareData.link;
var result = shareData.result;
//do something
//这里模拟异步操作
setTimeout(function(){
//2s之后,回调true分享成功
result(true);
}, 2000);
}
//调用的时候需要这么写
share({
title: "title",
imgUrl: "http://img.dd.com/xxx.png",
link: location.href,
result: function(res) { //函数作为参数
console.log(res ? "success" : "failure");
}
});在原生中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//异步回调
context[@"share"] = ^(JSValue *shareData) { //首先这里要注意,回调的参数不能直接写NSDictionary类型,为何呢?
//仔细看,打印出的确实是一个NSDictionary,但是result字段对应的不是block而是一个NSDictionary
NSLog(@"%@", [shareData toObject]);
//获取shareData对象的result属性,这个JSValue对应的其实是一个javascript的function。
JSValue *resultFunction = [shareData valueForProperty:@"result"];
//回调block,将js的function转换为OC的block
void (^result)(BOOL) = ^(BOOL isSuccess) {
[resultFunction callWithArguments:@[@(isSuccess)]];
};
//模拟异步回调
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"回调分享成功");
result(YES);
});
};
3. Cookie
在webView中Cookie可以保证我们再次访问h5的时候,记录之前的一些信息,比如登录信息等。
UIWebView
的Cookie
管理很简单,一般不需要我们手动操作Cookie
,因为所有Cookie
都会被[NSHTTPCookieStorage sharedHTTPCookieStorage]
这个单例管理,而且UIWebView
会自动同步CookieStorage
中的Cookie,所以只要我们在Native端,正常登陆退出,h5在适当时候刷新,就可以正确的维持登录状态,不需要做多余的操作。
可能有一些情况下,我们需要在访问某个链接时,添加一个固定Cookie
用来做区分,那么就可以通过header
来实现
1 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]]; |
也可以主动操作NSHTTPCookieStorage
,添加一个自定义Cookie
1 | NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{ |
以上~
WKWebView的可以参考:WKWebView整理篇