最近在做的項目遇到這么一個需求:網(wǎng)絡(luò)加載一段HTML格式的只有body部分的文本,自己本地拼寫完整的HTML,圖片需帶點擊放大瀏覽功能(移動端實現(xiàn))。
分析需求之后發(fā)現(xiàn)難點在如何與UIWebview進(jìn)行交互,在查閱資料后,找到http://kittenyang.com/webview-javascript-bridge
KittenYang大神完美解決了我的問題,但過程中還是出了不少問題,這里就寫下按照大神的思路寫的具體實現(xiàn)過程
這里主要用了WebViewJavascriptBridge,SDWebImage,YYPhotoGroup這三個第三方庫。
WebViewJavascriptBridge使用簡介
HTML文本的<script></script>標(biāo)簽中間添加如下代碼
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 = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)}
setupWebViewJavascriptBridge(function(bridge) {
//方法名,傳遞的參數(shù),回調(diào)
bridge.callHandler('testObjcCallback', 'Hello world', function(response)){
};
//方法名,收到的參數(shù),回調(diào)方法
bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
var message = data['foo']
console.log(message)
})
在OC端需實現(xiàn)
@property(strong,nonatomic) WebViewJavascriptBridge* bridge;
_bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
//注冊方法
[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"testObjcCallback called: %@", data);
responseCallback(@"Response from testObjcCallback");
}];
//調(diào)用JS的方法并傳參
[_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }];
完成上面的步驟后就可以實現(xiàn)與UIWebView的JS交互,但是要實現(xiàn)需求中所提到的功能還需要更深層次的交互,還需要一定的前端知識。
流程設(shè)計

流程圖
功能實現(xiàn)
-
HTML文本處理
//獲取本地HTML路徑 NSString* HTMLPath = [[NSBundle mainBundle] pathForResource:@"HTMLDemo" ofType:@"html"]; //獲取HTML字符串 NSMutableString* HTMLString = [NSMutableString stringWithContentsOfFile:HTMLPath encoding:NSUTF8StringEncoding error:nil]; //設(shè)定需要替換的字符串 NSRange range = [HTMLString rangeOfString:@"<P>mainviews</P>"]; //將字符串中的src標(biāo)簽都替換掉,防止加載HTML的時候預(yù)加載圖片 NSString *replace = [self.HTMLBoday stringByReplacingOccurrencesOfString:@"src=" withString:@"esrc="]; //將字符串替換調(diào) [HTMLString replaceOccurrencesOfString:@"<P>mainviews</P>" withString:replace options:NSCaseInsensitiveSearch range:range]; //根據(jù)正則表達(dá)式查找img標(biāo)簽 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(<img[ ^>]+esrc=\")(\\S+)\"" options:0 error:nil]; //給img標(biāo)簽添加點擊事件 NSString *finalHTMLString = [regex stringByReplacingMatchesInString:HTMLString options:0 range:NSMakeRange(0, HTMLString.length) withTemplate:@"<img esrc=\"$2\" onClick=\"javascript:onImageClick('$2')\" "]; //加載HTML [self.webView loadHTMLString:finalHTMLString baseURL:[NSURL fileURLWithPath:HTMLPath]]; -
獲取所有img的src
JS端bridge.registerHandler('getImageUrlsArray', function(data, responseCallback) { //獲取所有img標(biāo)簽 var allImage = document.querySelectorAll("img"); //Array.prototype.slice.call(arguments)能將具有l(wèi)ength屬性的對象轉(zhuǎn)成數(shù)組 allImage = Array.prototype.slice.call(allImage, 0); var imageUrlsArray = new Array(); allImage.forEach(function(image) { //將所有圖片的URL存入新數(shù)組 var esrc = image.getAttribute("esrc"); var newLength = imageUrlsArray.push(esrc); }); //將URL數(shù)組回傳OC端 responseCallback({'data':imageUrlsArray}) })OC端
[self.bridge callHandler:@"getImageUrlsArray" data:nil responseCallback:^(id responseData) { //拿到img的所有url并開始下載 [self demo_downloadImages:responseData[@"data"]]; }]; -
下載圖片
SDWebImageManager *manager = [SDWebImageManager sharedManager]; //假如沒有截取圖片則不執(zhí)行方法 if (imageUrlArray.count == 0) { return; } //預(yù)防數(shù)組越界處理 for (NSUInteger i = 0; i < imageUrlArray.count; i++) { [self.downloadImageArray addObject:[NSNull null]]; } for (NSUInteger i = 0; i < imageUrlArray.count; i++) { NSString *_url = imageUrlArray[i]; //SDWebImage下載圖片 [manager downloadImageWithURL:[NSURL URLWithString:_url] options:SDWebImageHighPriority progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (image) { //拿到存儲的key NSString *key = [manager cacheKeyForURL:imageURL]; //將url轉(zhuǎn)換成string NSString *old = [imageURL absoluteString]; //根據(jù)key拿到本地存儲路徑 NSString *localCachePath = [manager.imageCache defaultCachePathForKey:key]; [self.downloadImageArray removeObjectAtIndex:i]; [self.downloadImageArray insertObject:localCachePath atIndex:i]; //將本地緩存圖片的地址傳到webView [self.bridge callHandler:@"imagesDownloadComplete" data:@{@"old":old,@"new":localCachePath} responseCallback:^(id responseData) { }]; } }]; } -
JS端加載圖片
bridge.registerHandler('imagesDownloadComplete',function(data) { //解析數(shù)據(jù),拿到原來的url和本地存儲地址 var oldUrl = data['old'] var localCachePath = data['new'] imagesDownloadComplete(oldUrl,localCachePath) }) var errorNum = 0 function imagesDownloadComplete(pOldUrl, localCachePath) { //獲取所以得img標(biāo)簽 var allImage = document.querySelectorAll("img"); allImage = Array.prototype.slice.call(allImage, 0); allImage.forEach(function(image) { if (image.getAttribute("esrc") == pOldUrl || image.getAttribute("esrc") == decodeURIComponent(pOldUrl)) { //加載本地圖片地址 image.src = localCachePath; //圖片加載錯誤時再加載 image.onerror = function(){ if (errorNum < 5) { errorNum ++; this.src = localCachePath; } console.log('圖片加載從錯誤重新加載') } } }); } -
點擊放大圖片
JS端function onImageClick(picUrl){ setupWebViewJavascriptBridge(function(bridge) { var allImage = document.querySelectorAll("img[esrc]"); allImage = Array.prototype.slice.call(allImage, 0); var urls = new Array(); var index = -1; var x = 0; var y = 0; var width = 0; var height = 0; allImage.forEach(function(image) { var imgUrl = image.getAttribute("esrc"); var newLength = urls.push(imgUrl); //獲取 if(imgUrl == picUrl || imgUrl == decodeURIComponent(picUrl)){ index = newLength-1; x = image.getBoundingClientRect().left; y = image.getBoundingClientRect().top; x = x + document.documentElement.scrollLeft; y = y + document.documentElement.scrollTop; width = image.width; height = image.height; console.log("x:"+x +";y:" + y+";width:"+image.width +";height:"+image.height); } }); console.log("檢測到點擊"); //將圖片在圖片數(shù)組中的index,frame回傳給OC端 bridge.callHandler('imageDidClicked', {'index':index,'x':x,'y':y,'width':width,'height':height}, function(response) { console.log("JS已經(jīng)發(fā)出imgurl和index,同時收到回調(diào),說明OC已經(jīng)收到數(shù)據(jù)"); }); }); }
OC端
圖片在UIWebView中的frame和本地存儲地址都能拿到,怎么實現(xiàn)放大功能就看自己的喜好了,這里我使用了YYPhotoGroup這個第三方庫。
[self.bridge registerHandler:@"imageDidClicked" handler:^(id data, WVJBResponseCallback responseCallback) {
NSInteger index = [[data objectForKey:@"index"] integerValue];
CGFloat originX = [[data objectForKey:@"x"] floatValue];
CGFloat originY = [[data objectForKey:@"y"] floatValue];
CGFloat width = [[data objectForKey:@"width"] floatValue];
CGFloat height = [[data objectForKey:@"height"] floatValue];
self.tappedImageView.frame = CGRectMake(originX, originY, width, height);
self.tappedImageView.image = [YYImage imageWithContentsOfFile:self.downloadImageArray[index]];
responseCallback(@"OC已經(jīng)收到JS的imageDidClicked了");
//點擊放大圖片
YYPhotoGroupItem *item = [[YYPhotoGroupItem alloc]init];
item.thumbView = self.tappedImageView;
item.largeImageURL = [NSURL URLWithString:self.downloadImageArray[index]];
YYPhotoGroupView *view = [[YYPhotoGroupView alloc]initWithGroupItems:@[item]];
view.blurEffectBackground = NO;
[view presentFromImageView:self.tappedImageView toContainer:[UIApplication sharedApplication].keyWindow animated:YES completion:^{
}];
}];
效果圖

demo.gif