最近在做的项目遇到这么一个需求:网络加载一段HTML格式的只有body部分的文本,自己本地拼写完整的HTML,图片需带点击放大浏览功能(移动端实现)。
分析需求之后发现难点在如何与UIWebview进行交互,在查阅资料后,找到http://kittenyang.com/webview-javascript-bridge
KittenYang大神完美解决了我的问题,但过程中还是出了不少问题,这里就写下按照大神的思路写的具体实现过程
这里主要用了WebViewJavascriptBridge,SDWebImage,YYPhotoGroup这三个第三方库。
WebViewJavascriptBridge使用简介
HTML文本的<script></script>标签中间添加如下代码
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) {
//方法名,传递的参数,回调
bridge.callHandler('testObjcCallback', 'Hello world', function(response)){
};
//方法名,收到的参数,回调方法
bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
var message = data['foo']
console.log(message)
})
在OC端需实现
@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");
}];
//调用JS的方法并传参
[_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }];
完成上面的步骤后就可以实现与UIWebView的JS交互,但是要实现需求中所提到的功能还需要更深层次的交互,还需要一定的前端知识。
流程设计
功能实现
-
HTML文本处理
//获取本地HTML路径 NSString* HTMLPath = [[NSBundle mainBundle] pathForResource:@"HTMLDemo" ofType:@"html"]; //获取HTML字符串 NSMutableString* HTMLString = [NSMutableString stringWithContentsOfFile:HTMLPath encoding:NSUTF8StringEncoding error:nil]; //设定需要替换的字符串 NSRange range = [HTMLString rangeOfString:@"<P>mainviews</P>"]; //将字符串中的src标签都替换掉,防止加载HTML的时候预加载图片 NSString *replace = [self.HTMLBoday stringByReplacingOccurrencesOfString:@"src=" withString:@"esrc="]; //将字符串替换调 [HTMLString replaceOccurrencesOfString:@"<P>mainviews</P>" withString:replace options:NSCaseInsensitiveSearch range:range]; //根据正则表达式查找img标签 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(<img[ ^>]+esrc=\")(\\S+)\"" options:0 error:nil]; //给img标签添加点击事件 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标签 var allImage = document.querySelectorAll("img"); //Array.prototype.slice.call(arguments)能将具有length属性的对象转成数组 allImage = Array.prototype.slice.call(allImage, 0); var imageUrlsArray = new Array(); allImage.forEach(function(image) { //将所有图片的URL存入新数组 var esrc = image.getAttribute("esrc"); var newLength = imageUrlsArray.push(esrc); }); //将URL数组回传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]; //假如没有截取图片则不执行方法 if (imageUrlArray.count == 0) { return; } //预防数组越界处理 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转换成string NSString *old = [imageURL absoluteString]; //根据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) { //解析数据,拿到原来的url和本地存储地址 var oldUrl = data['old'] var localCachePath = data['new'] imagesDownloadComplete(oldUrl,localCachePath) }) var errorNum = 0 function imagesDownloadComplete(pOldUrl, localCachePath) { //获取所以得img标签 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("检测到点击"); //将图片在图片数组中的index,frame回传给OC端 bridge.callHandler('imageDidClicked', {'index':index,'x':x,'y':y,'width':width,'height':height}, function(response) { console.log("JS已经发出imgurl和index,同时收到回调,说明OC已经收到数据"); }); }); }
OC端
图片在UIWebView中的frame和本地存储地址都能拿到,怎么实现放大功能就看自己的喜好了,这里我使用了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已经收到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:^{
}];
}];
效果图