前言
一直想系統(tǒng)的總結(jié)下UIWebView
和WKWebView
泊藕,這里整理了一個(gè)
Demo可供參考
分為兩部分:
UIWebView & WKWebView 上
UIWebView & WKWebView 下
OC-->JS
UIWebView OC-->JS
- 1、通過調(diào)用
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
方法 - 2宇植、在頁面加載完成后,獲取JSContext上下文埋心,通過JSContext的
- (JSValue *)evaluateScript:(NSString *)script;
方法得到JSValue對(duì)象,JSValue對(duì)象可轉(zhuǎn)為Array忙上、Number拷呆、String、對(duì)象等數(shù)據(jù)類型
WKWebView OC-->JS
通過調(diào)用方法:- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id result, NSError * _Nullable error))completionHandler;
UIWebView OC-->JS解析
-
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
使用:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//@property (nonatomic, strong) UIWebView * webView;
self.title = [self.webView stringByEvaluatingJavaScriptFromString:@"document.title"];
}
1疫粥、該方法不能判斷調(diào)用了一個(gè)js方法之后茬斧,是否發(fā)生了錯(cuò)誤。當(dāng)錯(cuò)誤發(fā)生時(shí)梗逮,返回值為nil项秉,而當(dāng)調(diào)用一個(gè)方法本身沒有返回值時(shí),返回值也為nil慷彤,所以無法判斷是否調(diào)用成功了娄蔼。
2怖喻、返回值類型為nullable NSString *,就意味著當(dāng)調(diào)用的js方法有返回值時(shí)岁诉,都以字符串返回锚沸,不夠靈活。當(dāng)返回值是一個(gè)js的Array時(shí)涕癣,還需要解析字符串哗蜈,比較麻煩。
-
- (JSValue *)evaluateScript:(NSString *)script;
使用:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//@property (nonatomic, strong) UIWebView * webView;
//@property (nonatomic, strong) JSContext * jsContext;
//獲取該UIWebview的javascript上下文
self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//JSContext oc調(diào)用js
JSValue *value = [self.jsContext evaluateScript:@"document.title"];
self.title = value.toString;
}
1坠韩、 其實(shí)WebKit都有一個(gè)內(nèi)嵌的js環(huán)境距潘,一般我們?cè)陧撁婕虞d完成之后,獲取js上下文只搁,然后通過JSContext的evaluateScript:
方法來獲取返回值音比。因?yàn)樵摲椒ǖ玫降氖且粋€(gè)JSValue對(duì)象,所以支持JavaScript的Array须蜗、Number硅确、String、對(duì)象等數(shù)據(jù)類型明肮。該方法解決了stringByEvaluatingJavaScriptFromString:
返回值只是NSString的問題菱农。
2、 [self.jsContext evaluateScript:@"document.titlexxxx"];
那么必然會(huì)報(bào)錯(cuò)柿估,報(bào)錯(cuò)了循未,可以通過 @property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);
,設(shè)置該block來獲取異常
//在調(diào)用前秫舌,設(shè)置異车难回調(diào)
[self.jsContext setExceptionHandler:^(JSContext *context, JSValue *exception){
NSLog(@"%@", exception);
}];
//執(zhí)行方法
JSValue *value = [self.jsContext evaluateScript:@"document.titlexxxx"];
該方法,也很好的解決了stringByEvaluatingJavaScriptFromString:調(diào)用js方法后足陨,出現(xiàn)錯(cuò)誤卻捕獲不到的缺點(diǎn)嫂粟。
WKWebView OC-->JS解析
//@property (strong, nonatomic) WKWebView *webView;
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
// self.title = self.webView.title;
// //執(zhí)行一段js,并將結(jié)果返回墨缘,如果出錯(cuò)星虹,error則不為空
[self.webView evaluateJavaScript:@"document.title" completionHandler:^(id _Nullable title, NSError * _Nullable error) {
self.title = title;
}];
}
該方法很好的解決了UIWebView
使用stringByEvaluatingJavaScriptFromString:
方法的兩個(gè)缺點(diǎn)(1. 返回值只能是NSString。2. 報(bào)錯(cuò)無法捕獲)镊讼。
JS-->0C
UIWebView JS-->0C
1宽涌、攔截URL
OC中,只要遵循了UIWebViewDelegate協(xié)議, 每次打開一個(gè)鏈接之前蝶棋,都會(huì)觸發(fā)方法- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
在該方法中卸亮,捕獲該鏈接,并且返回NO(阻止本次跳轉(zhuǎn))玩裙,從而執(zhí)行對(duì)應(yīng)的OC方法兼贸。
2段直、self.jsContext[@"yourMethodName"] = your block;
其中yourMethodName就是js的方法名稱,賦給是一個(gè)block 里面是oc代碼
WKWebView JS-->OC
1寝受、URL攔截
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
2坷牛、WKUserContentController
中新增方法
- 注冊(cè)回調(diào)
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
- js中調(diào)用方法
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
- oc中將會(huì)收到
WKScriptMessageHandler
的回調(diào)
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
- 移除
- (void)removeScriptMessageHandlerForName:(NSString *)name;
攔截URL解析
1、Html
中實(shí)現(xiàn)
比如syhcandy://
方法是在html或者js中很澄,點(diǎn)擊某個(gè)按鈕觸發(fā)事件時(shí)京闰,跳轉(zhuǎn)到自定義URL Scheme構(gòu)成的鏈接,而Objective-C中捕獲該鏈接甩苛,從中解析必要的參數(shù)蹂楣,實(shí)現(xiàn)JS到OC的一次交互。比如頁面中一個(gè)a標(biāo)簽讯蒲,鏈接如下:
<a href="syhcandy://smsLogin?username=syh&code=776632">短信驗(yàn)證登錄</a>
UIWebView的JS-->OC中URL攔截實(shí)現(xiàn)
而在Objective-C中痊土,只要遵循了UIWebViewDelegate
協(xié)議,那么每次打開一個(gè)鏈接之前墨林,都會(huì)觸發(fā)方法- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
在該方法中赁酝,捕獲該鏈接,并且返回NO(阻止本次跳轉(zhuǎn))旭等,從而執(zhí)行對(duì)應(yīng)的OC方法酌呆。
#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
//標(biāo)準(zhǔn)的URL包含scheme、host搔耕、port隙袁、path、query弃榨、fragment等
NSURL *URL = request.URL;
if ([URL.scheme isEqualToString:SHWebViewDemoScheme]) {
if ([URL.host isEqualToString:SHWebViewDemoHostSmsLogin]) {
NSLog(@"短信驗(yàn)證碼登錄菩收,參數(shù)為 %@", URL.query); //短信驗(yàn)證碼登錄,參數(shù)為 username=syh&code=776632
return NO;
}
}
return YES;
}
缺點(diǎn):無法直接獲取本次交互的返回值鲸睛,比較適合單向傳參娜饵,且不關(guān)心回調(diào)的情景,比如h5頁面跳轉(zhuǎn)到native頁面等官辈。
WKWebView JS-->OC中URL攔截實(shí)現(xiàn)
當(dāng)用戶點(diǎn)擊這個(gè)a標(biāo)簽時(shí)箱舞,會(huì)被攔截
//針對(duì)一次action來決定是否允許跳轉(zhuǎn),允許與否都需要調(diào)用decisionHandler钧萍,比如decisionHandler(WKNavigationActionPolicyCancel);
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
//可以通過navigationAction.navigationType獲取跳轉(zhuǎn)類型,如新鏈接政鼠、后退等
NSURL *URL = navigationAction.request.URL;
//判斷URL是否符合自定義的URL Scheme
if([URL.scheme isEqualToString:SHWebViewDemoScheme]){
//根據(jù)不同的業(yè)務(wù)风瘦,來執(zhí)行對(duì)應(yīng)的操作,且獲取參數(shù)
if([URL.host isEqualToString:SHWebViewDemoHostSmsLogin]){
NSString *param = URL.query;
NSLog(@"短信驗(yàn)證碼登錄, 參數(shù)為%@", param);//短信驗(yàn)證碼登錄, 參數(shù)為username=12323123&code=892845
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
}
decisionHandler(WKNavigationActionPolicyAllow);
}
html中定義了分享方法如下:
<div>
<a href="javascript:void(0);" class="sharebtn" onclick="share('分享標(biāo)題', 'http://cc.cocimg.com/api/uploads/170425/b2d6e7ea5b3172e6c39120b7bfd662fb.jpg', location.href)">分享活動(dòng)公般,領(lǐng)30元紅包</a>
</div>
<script>
//簡單分享
function share (title, imgUrl, link) {
//便于WKWebView測試
window.webkit.messageHandlers.share.postMessage({title: title, imgUrl: imgUrl, link: link});
//這里需要OC實(shí)現(xiàn)
}
</script>
UIWebView JS-->OC中OC實(shí)現(xiàn)
self.jsContext[@"yourMethodName"] = your block;
方法映射
在頁面加載完成時(shí)万搔,先獲取js上下文胡桨。獲取到之后,我們就可以進(jìn)行強(qiáng)大的方法映射了瞬雹。
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[self convertJSFunctionsToOCMethods];
}
- (void)convertJSFunctionsToOCMethods
{
//其中share就是js的方法名稱昧谊,賦給是一個(gè)block 里面是iOS代碼
//此方法最終將打印出所有接收到的參數(shù),js參數(shù)是不固定的
self.jsContext[@"share"] = ^(){
NSArray *args = [JSContext currentArguments];//獲取到share里的所有參數(shù)
//args中的元素是JSValue酗捌,需要轉(zhuǎn)成OC的對(duì)象
NSMutableArray *messages = [NSMutableArray array];
for (JSValue *obj in args) {
[messages addObject:[obj toObject]];
}
NSLog(@"點(diǎn)擊分享js傳回的參數(shù):\n%@", messages);
/**
點(diǎn)擊分享js傳回的參數(shù):
(
"\U5206\U4eab\U6807\U9898",
"http://cc.cocimg.com/api/uploads/170425/b2d6e7ea5b3172e6c39120b7bfd662fb.jpg",
"file:///Users/macair/Library/Developer/CoreSimulator/Devices/04C1A1B2-EBF1-4C3A-BC06-6664428718F6/data/Containers/Bundle/Application/2684F36D-58CB-4A37-96AB-334D21098682/WebViewDemo.app/test.html"
)
*/
};
}
scriptMessageHandler
WKUserContentController.h
新增兩個(gè)方法如下:
//在OC中添加一個(gè)scriptMessageHandler呢诬,則會(huì)在all frames中添加一個(gè)js的function: window.webkit.messageHandlers.<name>.postMessage(<messageBody>) 。
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
- (void)removeScriptMessageHandlerForName:(NSString *)name;
WKWebView JS-->OC中OC實(shí)現(xiàn)
- 在OC中添加一個(gè)handler
//WKUserContentController *UserContentController = [[WKUserContentController alloc] init];
//注冊(cè)回調(diào)
[UserContentController addScriptMessageHandler:self name:@"share"];
- js調(diào)用
share
方法后胖缤,OC會(huì)收到WKScriptMessageHandler
回調(diào)
#pragma mark - WKScriptMessageHandler js -> oc
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
if ([message.name isEqualToString:@"share"]) {
id body = message.body;
NSLog(@"share分享的內(nèi)容為:%@", body);
/**
share分享的內(nèi)容為:{
imgUrl = "http://cc.cocimg.com/api/uploads/170425/b2d6e7ea5b3172e6c39120b7bfd662fb.jpg";
link = "file:///Users/macair/Library/Developer/CoreSimulator/Devices/04C1A1B2-EBF1-4C3A-BC06-6664428718F6/data/Containers/Bundle/Application/C009579D-10B9-49D2-A3A4-4D409157C158/WebViewDemo.app/test.html";
title = "\U5206\U4eab\U6807\U9898";
}
*/
}
}
- 記得移除注冊(cè)的回調(diào)
- (void)dealloc
{
//記得移除
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"share"];
}
需求:寫一個(gè)有兩個(gè)參數(shù)尚镰,一個(gè)返回值的js方法,oc應(yīng)該怎么替換呢哪廓?
Html中
<div onclick="alert(testAddMethod(1,5))">點(diǎn)擊測試兩數(shù)相加</div>
<script>
//該方法傳入兩個(gè)整數(shù)狗唉,求和,并返回結(jié)果
function testAddMethod (a, b) {
//需要OC實(shí)現(xiàn)a+b涡真,并返回
return a + b;
}
</script>
UIWebView中對(duì)應(yīng)的OC替換該方法的實(shí)現(xiàn):
self.jsContext[@"testAddMethod"] = ^NSInteger(NSInteger a, NSInteger b){
return a + b;
// return a * b;
};
需求升級(jí):調(diào)用方法原實(shí)現(xiàn)分俯,并且在原結(jié)果上乘以10
//調(diào)用方法的本來實(shí)現(xiàn),給原結(jié)果乘以10
JSValue *value = self.jsContext[@"testAddMethod"];
self.jsContext[@"testAddMethod"] = ^NSInteger(NSInteger a, NSInteger b){
JSValue *resultValue = [value callWithArguments:[JSContext currentArguments]];
return resultValue.toInt32 * 10;
};
新需求:h5中有一個(gè)分享按鈕哆料,用戶點(diǎn)擊之后缸剪,調(diào)用native分享(微信分享、微博分享等)剧劝,在native分享成功或者失敗時(shí)橄登,回調(diào)h5頁面,告訴其分享結(jié)果讥此,h5頁面刷新對(duì)應(yīng)的UI拢锹,顯示分享成功或者失敗。
Html的代碼
<a href="javascript:void(0);" onclick="test()">測試新分享</a></br>
<h>下面展示分享結(jié)果</p><div id="shareResult"></div>
<script>
/**
* 分享方法萄喳,并且會(huì)異步回調(diào)分享結(jié)果
* @param {對(duì)象類型} shareData 一個(gè)分享數(shù)據(jù)的對(duì)象卒稳,包含title,imgUrl,link以及一個(gè)回調(diào)function
* @return {void} 無同步返回值
js的shareNew方法的參數(shù)是一個(gè)對(duì)象,該對(duì)象包含了幾個(gè)必要的字段他巨,以及一個(gè)回調(diào)函數(shù)充坑,這個(gè)回調(diào)函數(shù)有點(diǎn)像oc的block,調(diào)用者把一個(gè)function傳入一個(gè)function當(dāng)作參數(shù)染突,在適當(dāng)時(shí)候捻爷,方法內(nèi)實(shí)現(xiàn)者調(diào)用該function,實(shí)現(xiàn)對(duì)調(diào)用者的異步回調(diào)份企。
*/
function shareNew(shareData) {
var title = shareData.title;
var imgUrl = shareData.imgUrl;
var link = shareData.link;
var result = shareData.result;
//do something
//這里模擬異步操作
setTimeout(function(){
//2s之后也榄,回調(diào)true分享成功
result(true);
}, 2000);
//用于WKWebView,因?yàn)閃KWebView并沒有辦法把js function傳遞過去,因此需要特殊處理一下
//把js function轉(zhuǎn)換為字符串甜紫,oc端調(diào)用時(shí) (<js function string>)(true); 即可
shareData.result = result.toString();
window.webkit.messageHandlers.shareNew.postMessage(shareData);
}
function test() {
//清空分享結(jié)果
shareResult.innerHTML = "";
//調(diào)用時(shí)降宅,應(yīng)該
shareNew({
title: "title",
imgUrl: "http://img.dd.com/xxx.png",
link: location.href,
result: function(res) {
//這里shareResult 等同于 document.getElementById("shareResult")
shareResult.innerHTML = res ? "success" : "failure";
}
});
}
</script>
上述html代碼解析:
點(diǎn)擊之后,觸發(fā)了test()
函數(shù)囚霸,test()
中封裝了對(duì)shareNew()
函數(shù)的調(diào)用腰根,且傳了一個(gè)對(duì)象作為參數(shù),對(duì)象中result字段對(duì)應(yīng)的是個(gè)匿名函數(shù)拓型,緊接著shareNew()
函數(shù)調(diào)用额嘿,其中的實(shí)現(xiàn)是2s過后,result(true)
;模擬js異步實(shí)現(xiàn)異步回調(diào)結(jié)果吨述,分享成功岩睁。同時(shí)shareNew()
函數(shù)中,因?yàn)橥ㄟ^scriptMessageHandler
無法傳遞function
揣云,所以先把shareData
對(duì)象中的result
這個(gè)匿名function
轉(zhuǎn)成String捕儒,然后替換shareData
對(duì)象的result屬性為這個(gè)String,并回傳給OC邓夕,OC這邊對(duì)應(yīng)JS對(duì)象的數(shù)據(jù)類型是NSDictionary刘莹,我們打印并得到了所有參數(shù),同時(shí)焚刚,把result字段對(duì)應(yīng)的js function String取出來点弯。這里我們延遲4s回調(diào),模擬Native分享的異步過程矿咕,在4s后抢肛,也就是js中顯示success的2s過后,調(diào)用js的匿名function碳柱,并傳遞參數(shù)(分享結(jié)果)捡絮。調(diào)用一個(gè)js function的方法是 functionName(argument);
,這里由于這個(gè)js的function已經(jīng)是一個(gè)String了莲镣,所以我們調(diào)用時(shí)福稳,需要加上(),如 (functionString)(argument);
因此瑞侮,最終我們通過OC -> JS 的evaluateJavaScript:completionHandler:
方法的圆,成功完成了異步回調(diào),并傳遞給js一個(gè)分享失敗的結(jié)果半火。
上面的描述看起來很復(fù)雜越妈,其實(shí)就是先執(zhí)行了JS的默認(rèn)實(shí)現(xiàn),后執(zhí)行了OC的實(shí)現(xiàn)钮糖。上面的代碼展示了如何解決scriptMessageHandler
的兩個(gè)問題梅掠,并且實(shí)現(xiàn)了一個(gè) JS -> OC、OC -> JS 完整的交互流程。
UIWebView在OC中的實(shí)現(xiàn):
- (void)convertJSFunctionsToOCMethods
{
self.jsContext[@"shareNew"] = ^(JSValue *shareData){//首先這里要注意瓤檐,回調(diào)的參數(shù)不能直接寫NSDictionary類型,為何呢娱节?
//仔細(xì)看挠蛉,打印出的確實(shí)是一個(gè)NSDictionary,但是result字段對(duì)應(yīng)的不是block而是一個(gè)NSDictionary
NSLog(@"%@", [shareData toObject]);
/**
{
imgUrl = "http://img.dd.com/xxx.png";
link = "file:///Users/macair/Library/Developer/CoreSimulator/Devices/04C1A1B2-EBF1-4C3A-BC06-6664428718F6/data/Containers/Bundle/Application/F0F701AB-D134-4596-8104-16EDD27CDBCD/WebViewDemo.app/test.html";
result = {
};
title = title;
}
*/
//獲取shareData對(duì)象的result屬性肄满,這個(gè)JSValue對(duì)應(yīng)的其實(shí)是一個(gè)javascript的function谴古。
JSValue *resultFunction = [shareData valueForProperty:@"result"];
//回調(diào)block,將js的function轉(zhuǎn)換為OC的block
void (^result)(BOOL) = ^(BOOL isSuccess) {
[resultFunction callWithArguments:@[@(isSuccess)]];
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
result(NO);
});
};
}
WKWebView在OC中的實(shí)現(xiàn)
- 注冊(cè)回調(diào)
//WKUserContentController *UserContentController = [[WKUserContentController alloc] init];
//注冊(cè)回調(diào)
[UserContentController addScriptMessageHandler:self name:@"shareNew"];
-
WKScriptMessageHandler
代理調(diào)用
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
if ([message.name isEqualToString:@"shareNew"]){
NSDictionary *shareData = message.body;
NSLog(@"%@分享的數(shù)據(jù)為: %@", message.name, shareData);
/**
shareNew分享的數(shù)據(jù)為: {
imgUrl = "http://img.dd.com/xxx.png";
link = "file:///Users/macair/Library/Developer/CoreSimulator/Devices/04C1A1B2-EBF1-4C3A-BC06-6664428718F6/data/Containers/Bundle/Application/9DE46251-970B-460B-AB13-86629C09C279/WebViewDemo.app/test.html";
result = "function (res) {\n //\U8fd9\U91ccshareResult \U7b49\U540c\U4e8e document.getElementById(\"shareResult\")\n shareResult.innerHTML = res ? \"success\" : \"failure\";\n\n }";
title = title;
}
*/
//模擬異步回調(diào)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//讀取js function的字符串
NSString *jsFunctionString = shareData[@"result"];
/*
function (res) {
//這里shareResult 等同于 document.getElementById("shareResult")
shareResult.innerHTML = res ? "success" : "failure";
}
*/
//拼接調(diào)用該方法的js字符串
NSString *callbackJs = [NSString stringWithFormat:@"(%@)(%d);", jsFunctionString, NO]; //后面的參數(shù)NO為模擬分享失敗
//執(zhí)行回調(diào)
[self.webView evaluateJavaScript:callbackJs completionHandler:^(id _Nullable result, NSError * _Nullable error) {
if (!error) {
NSLog(@"模擬回調(diào)稠歉,分享失敗");
}
}];
});
}
}
- 移除注冊(cè)
- (void)dealloc
{
//記得移除
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"shareNew"];
}
Native預(yù)覽H5頁面中的image
場景:在微信中瀏覽網(wǎng)頁時(shí)掰担,看到喜歡的圖片,你會(huì)點(diǎn)擊圖片查看大圖怒炸,然后長按圖片保存带饱。
分析
1、如果想在Native預(yù)覽H5中的image阅羹,最需要的是什么勺疼?是圖片的鏈接。如果能有縮略圖更好了捏鱼。
2执庐、只要獲取了鏈接,就可以跳轉(zhuǎn)到一個(gè)ViewController中导梆,預(yù)覽圖片轨淌,后續(xù)長按保存自然水到渠成。
3看尼、那應(yīng)該如何獲取圖片的鏈接呢递鹉?通過JS -> OC 傳遞圖片url
方案
當(dāng)頁面加載完成后,給html頁面中所有無默認(rèn)點(diǎn)擊事件的<img>添加點(diǎn)擊事件狡忙,當(dāng)用戶點(diǎn)擊時(shí)梳虽,拿到所有參數(shù)。
(其實(shí)這不是最好的方案灾茁,最好的解決方案是窜觉,跟前端約定一下,哪些圖片需要預(yù)覽北专,哪些img標(biāo)簽的id統(tǒng)一禀挫,或者有個(gè)特定的屬性,這樣客戶端可以根據(jù)id找到這些img標(biāo)簽)
Html中有img標(biāo)簽
<img src="http://cc.cocimg.com/api/uploads/170425/b2d6e7ea5b3172e6c39120b7bfd662fb.jpg">
寫好一個(gè)ImgAddClickEvent.js
文件拓颓,來實(shí)現(xiàn)給所有無默認(rèn)點(diǎn)擊事件的<img>添加點(diǎn)擊事件语婴。
//獲取所有img標(biāo)簽
var imgs = document.getElementsByTagName("img");
//獲取所有的imgUrl
var imgUrls = new Array();
var x = 0;
var y = 0;
var width = 0;
var height = 0;
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
//如果圖片鏈接存在
if (img.src || img.getAttribute('data-src')) {
//添加到圖片鏈接數(shù)組中
imgUrls.push(img.src || img.getAttribute('data-src'));
//如果圖片沒有默認(rèn)的onclick事件,且父元素不是a標(biāo)簽,則添加onclick事件砰左,當(dāng)用戶點(diǎn)擊時(shí)匿醒,把圖片鏈接回傳給Native
if (!img.onclick && img.parentElement.tagName !== "A") {
//給圖片添加下標(biāo)的屬性
img.index = i; //記錄下標(biāo)
//添加點(diǎn)擊事件,并且回傳選中的圖片鏈接缠导、下標(biāo)廉羔、屏幕上的位置、全部的圖片數(shù)組等
img.onclick = function() {
x = this.getBoundingClientRect().left;
y = this.getBoundingClientRect().top;
x = x + document.documentElement.scrollLeft;
y = y + document.documentElement.scrollTop;
width = this.width;
height = this.height;
var imgInfo = {
imgUrl: this.src || this.getAttribute('data-src'),
x: x,
y: y,
width: width,
height: height,
index: this.index,
imgUrls: imgUrls
};
//UIWebView使用
h5ImageDidClick(imgInfo);
}
}
}
}
function h5ImageDidClick (info){
//WKWebView使用
window.webkit.messageHandlers.imageDidClick.postMessage(info);
}
UIWebView實(shí)現(xiàn)
UIWebView
直接使用JavaScriptCore
給<img>
添加onclick
方法為OC的實(shí)現(xiàn)即可僻造。
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[self convertJSFunctionsToOCMethods];
}
- (void)convertJSFunctionsToOCMethods
{
//獲取該UIWebview的javascript上下文
self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
/*
Native預(yù)覽H5頁面中的image
*/
//防止頻繁IO操作憋他,造成性能影響
static NSString *jsSource;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
jsSource = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ImgAddClickEvent" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil];
});
//先注入給圖片添加點(diǎn)擊事件的js
[self.jsContext evaluateScript:jsSource];
self.jsContext[@"h5ImageDidClick"] = ^(NSDictionary *imgInfo){
NSLog(@"UIWebView點(diǎn)擊了html上的圖片,信息是:%@", imgInfo);
};
/**
UIWebView點(diǎn)擊了html上的圖片髓削,信息是:{
height = 168;
imgUrl = "http://cc.cocimg.com/api/uploads/170425/b2d6e7ea5b3172e6c39120b7bfd662fb.jpg";
imgUrls = (
"http://cc.cocimg.com/api/uploads/170425/b2d6e7ea5b3172e6c39120b7bfd662fb.jpg"
);
index = 0;
width = 252;
x = 8;
y = 8;
}
*/
}
WKWebView實(shí)現(xiàn)
添加腳本
/**
Native預(yù)覽H5頁面中的image,
頁面中的所有img標(biāo)簽添加點(diǎn)擊事件
*/
- (void)imgAddClickEvent
{
//防止頻繁IO操作竹挡,造成性能影響
static NSString *jsSource;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
jsSource = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ImgAddClickEvent" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil];
});
/*
注入的js source可以是任何js字符串,也可以js文件立膛。比如你有很多提供給h5使用的js方法揪罕,那么你本地可能就會(huì)有一個(gè)ImgAddClickEvent.js
*/
//添加自定義的腳本
WKUserScript *js = [[WKUserScript alloc] initWithSource:jsSource injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
[self.webView.configuration.userContentController addUserScript:js];
//注冊(cè)回調(diào)
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"imageDidClick"];
}
注冊(cè)回調(diào)后的代理方法
#pragma mark - WKScriptMessageHandler js -> oc
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
if ([message.name isEqualToString:@"imageDidClick"]) {
//點(diǎn)擊了html上的圖片, Native預(yù)覽H5頁面中的image
NSLog(@"點(diǎn)擊了html上的圖片宝泵,參數(shù)為%@", message.body);
/**
點(diǎn)擊了html上的圖片耸序,參數(shù)為{
height = 168;
imgUrl = "http://cc.cocimg.com/api/uploads/170425/b2d6e7ea5b3172e6c39120b7bfd662fb.jpg";
imgUrls = (
"http://cc.cocimg.com/api/uploads/170425/b2d6e7ea5b3172e6c39120b7bfd662fb.jpg"
);
index = 0;
width = 252;
x = 8;
y = 8;
}
注意這里的x,y是不包含自定義scrollView的contentInset的鲁猩,如果要獲取圖片在屏幕上的位置:
x = x + self.webView.scrollView.contentInset.left;
y = y + self.webView.scrollView.contentInset.top;
*/
NSDictionary *dict = message.body;
NSString *selectedImageUrl = dict[@"imgUrl"];
CGFloat x = [dict[@"x"] floatValue] + + self.webView.scrollView.contentInset.left;
CGFloat y = [dict[@"y"] floatValue] + self.webView.scrollView.contentInset.top;
CGFloat width = [dict[@"width"] floatValue];
CGFloat height = [dict[@"height"] floatValue];
CGRect frame = CGRectMake(x, y, width, height);
NSUInteger index = [dict[@"index"] integerValue];
NSLog(@"點(diǎn)擊了第%@個(gè)圖片坎怪,\n鏈接為%@,\n在Screen中的絕對(duì)frame為%@廓握,\n所有的圖片數(shù)組為%@", @(index), selectedImageUrl, NSStringFromCGRect(frame), dict[@"imgUrls"]);
/*
點(diǎn)擊了第0個(gè)圖片搅窿,
鏈接為http://cc.cocimg.com/api/uploads/170425/b2d6e7ea5b3172e6c39120b7bfd662fb.jpg,
在Screen中的絕對(duì)frame為{{8, 72}, {252, 168}}隙券,
所有的圖片數(shù)組為(
"http://cc.cocimg.com/api/uploads/170425/b2d6e7ea5b3172e6c39120b7bfd662fb.jpg"
)
*/
}
}
移除
- (void)dealloc
{
//記得移除
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"imageDidClick"];
}
Native為H5提供一套Native Api(微信男应、支付寶小程序)
很多時(shí)候,Native與H5交互得深了娱仔,必定會(huì)有一些更深層次的需求沐飘。比如h5想控制頁面的pop、push牲迫、present耐朴,想調(diào)用Native的Share,想調(diào)用Native的掃描二維碼功能盹憎,獲取掃描結(jié)果……
微信提供的一些Api(掃碼筛峭、選擇照片等)都是如何實(shí)現(xiàn)的呢?很明顯陪每,native提供的影晓。
首先镰吵,我們?yōu)镠5提供一套Api,那自然Api是暴露給js的挂签,所以這些Api也是js的疤祭。下面針對(duì)一些需求,分析下封裝和實(shí)現(xiàn)饵婆。其中用到了js閉包画株,需要一點(diǎn)js知識(shí)。
從通訊錄選擇聯(lián)系人
從通訊錄選擇聯(lián)系人的例子啦辐,從js到native,再從native到j(luò)s蜈项。
js端實(shí)現(xiàn)
/**
* Native為H5提供的Api接口
*
* @type {js對(duì)象}
*/
var DANativeApi = (function() {
var NativeApi = {
/**
* 從通訊錄選擇聯(lián)系人
* @return {void} 無同步返回值芹关,異步返回選擇的結(jié)果
*/
choosePhoneContact: function(param) {
//具體是否需要判斷
//調(diào)用native端
_nativeChoosePhoneContact(param);
}
}
//下面是一些私有函數(shù)
/**
* Native端實(shí)現(xiàn)選擇聯(lián)系人,并異步返回結(jié)果
* @param {[type]} param [description]
* @return {[type]} [description]
*/
function _nativeChoosePhoneContact(param) {
var callbackFunction = param.completion;
if (callbackFunction != undefined && callbackFunction != null && typeof(callbackFunction) === "function") {
param.completion = callbackFunction.toString();
}
//js -> oc
window.webkit.messageHandlers.nativeChoosePhoneContact.postMessage(param);
}
//閉包紧卒,把Api對(duì)象返回
return NativeApi;
})();
/*
//選擇聯(lián)系人
DANativeApi.choosePhoneContact({
completion: function(res) {
alert("選擇聯(lián)系人的結(jié)果為:" + JSON.stringify(res));
}
});
*/
Html中調(diào)用
<div>
<a href="javascript:void(0);" onclick="chooseContact()">選擇聯(lián)系人</a>
<div id="contactInfo"></div>
</div>
<script>
function chooseContact() {
DANativeApi.choosePhoneContact({
completion: function(res) {
contactInfo.innerHTML = JSON.stringify(res);
}
});
}
</script>
WKWebView實(shí)現(xiàn)
- 添加腳本并注冊(cè)回調(diào)
/**
添加native端的api
*/
- (void)addNativeApiToJS
{
//防止頻繁IO操作侥衬,造成性能影響
static NSString *nativejsSource;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
nativejsSource = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"NativeApi" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil];
});
//添加自定義的腳本
WKUserScript *js = [[WKUserScript alloc] initWithSource:nativejsSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[self.webView.configuration.userContentController addUserScript:js];
//注冊(cè)回調(diào)
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"nativeChoosePhoneContact"];
}
-
WKScriptMessageHandler
回調(diào)代理方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
if ([message.name isEqualToString:@"nativeChoosePhoneContact"]) {
NSLog(@"正在選擇聯(lián)系人");
[self selectContactCompletion:^(NSString *name, NSString *phone) {
NSLog(@"選擇完成");
//讀取js function的字符串
NSString *jsFunctionString = message.body[@"completion"];
//拼接調(diào)用該方法的js字符串
NSString *callbackJs = [NSString stringWithFormat:@"(%@)({name: '%@', mobile: '%@'});", jsFunctionString, name, phone];
//執(zhí)行回調(diào)
[self.webView evaluateJavaScript:callbackJs completionHandler:^(id _Nullable result, NSError * _Nullable error) {
}];
}];
}
}
- 獲取聯(lián)系方式的oc實(shí)現(xiàn)
//<CNContactPickerDelegate>
//@property (nonatomic, copy) void(^completion)(NSString *name, NSString *phone);
#pragma mark 選擇聯(lián)系人
- (void)selectContactCompletion:(void(^)(NSString *name, NSString *phone))completion
{
self.completion = completion;
CNContactPickerViewController *picker = [[CNContactPickerViewController alloc] init];
picker.delegate = self;
picker.displayedPropertyKeys = @[CNContactPhoneNumbersKey];
[self presentViewController:picker animated:YES completion:^{
}];
}
#pragma mark - CNContactPickerDelegate
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty
{
if (![contactProperty.key isEqualToString:CNContactPhoneNumbersKey]) {
return;
}
CNContact *contact = contactProperty.contact;
NSString *name = [CNContactFormatter stringFromContact:contact style:CNContactFormatterStyleFullName];
CNPhoneNumber *phoneNumber = contactProperty.value;
NSString *phone = phoneNumber.stringValue.length ? phoneNumber.stringValue : @"";
//可以把-、+86跑芳、空格這些過濾掉
NSString *phoneStr = [phone stringByReplacingOccurrencesOfString:@"-" withString:@""];
phoneStr = [phoneStr stringByReplacingOccurrencesOfString:@"+86" withString:@""];
phoneStr = [phoneStr stringByReplacingOccurrencesOfString:@" " withString:@""];
phoneStr = [[phoneStr componentsSeparatedByCharactersInSet:[[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet]] componentsJoinedByString:@""];
//回調(diào)
if (self.completion) {
self.completion(name, phoneStr);
}
//dissMiss
[picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)contactPickerDidCancel:(CNContactPickerViewController *)picker
{
[picker dismissViewControllerAnimated:YES completion:nil];
}
- 移除注冊(cè)的回調(diào)
- (void)dealloc
{
//記得移除
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"nativeChoosePhoneContact"];
}