使用WebViewJavascriptBridge实现与UIWebView的深度交互

最近在做的项目遇到这么一个需求:网络加载一段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交互,但是要实现需求中所提到的功能还需要更深层次的交互,还需要一定的前端知识。

流程设计

流程图

功能实现

  1. 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]];
    
  2. 获取所有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"]];
     }];
    
  3. 下载图片

     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) {
             }];
           }
         }];        }
    
  4. 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('图片加载从错误重新加载')
          }
      }
     });
     }
    
  5. 点击放大图片
    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:^{
        
    }];
    }];

效果图

demo.gif

demo下载https://github.com/YuZhenKao/UIWebView-Demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容