無標題文章

事情的起因還是因為項目需求驅動。折騰了兩天,由于之前沒有UIWebView與JS交互的經歷,并且覺得這次在功能上有一定的創造性,特此留下一點文字,方便日后回顧。

我要實現這樣一個需求:按照本地的CSS文件展示一串網絡獲取的帶HTML格式的只有body部分的文本,需要自己拼寫完整的HTML。除此之外,還需要禁用獲取的HTML文本中自帶的 《 img 》 標簽自動加載,并把下載圖片的操作放在native端來處理,并通過JS將圖片在Cache中的地址返回給UIWebview。

之所以要把圖片操作放在native端做的好處在于:

1、可以進行本地緩存,下次進入這篇文章可以直接從緩存讀取,提高響應速度并且節省用戶流量。

2、可以實現點擊圖片放大、保存圖片到相冊等操作。

技術難點也有兩個:

1、如何讓HTML文本onLoad的時候,禁用自身的圖片加載而是從本地獲取圖片?

2、如何把native端下載好的圖片返回給網頁?

起初,我也是束手無策,翻看文檔可只找到了一個 - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script 和JS簡易交互的方法,未能如愿。直到我在Github上看到了WebViewJavascriptBridge 這個用于UIWebView/WebViews和JS交互的封裝庫。

剛看sample的時候我差點沒被各種回調搞暈,好記性不如爛筆頭,我從來不掩飾自己的愚笨,所以我畫了一個關系圖。在放圖之前,我們先看代碼。

一開始,我們在Native端和JS端都分別進行初始化:

OC端:

1 @property WebViewJavascriptBridge* bridge;

對應的初始化代碼如下,在初始化中直接包含了一個用于接收JS的回調:

1 _bridge = [WebViewJavascriptBridge bridgeForWebView:webView webViewDelegate:self handler:^(id data, WVJBResponseCallback responseCallback) {

2? ? ? ? NSLog(@"ObjC received message from JS: %@", data);

3? ? ? ? responseCallback(@"Response for message from ObjC");

4 }];

JS端:(以下是固定寫法,你自己的JS文件中必須包含如下代碼)

復制代碼

復制代碼

1 function connectWebViewJavascriptBridge(callback) {

2? ? if (window.WebViewJavascriptBridge) {

3? ? ? ? callback(WebViewJavascriptBridge)

4? ? } else {

5? ? ? ? document.addEventListener('WebViewJavascriptBridgeReady',? function() {

6? ? ? ? ? ? callback(WebViewJavascriptBridge)

7? ? ? ? }, false)

8? ? }

9 }

10 connectWebViewJavascriptBridge(function(bridge) {

11? ? bridge.init(function(message, responseCallback) {

12? ? ? ? log('JS got a message', message)

13? ? ? ? var data = { 'Javascript Responds':'Wee!' }

14? ? ? ? log('JS responding with', data)

15? ? ? ? responseCallback(data)

16? ? })

17 }

復制代碼

復制代碼

然后,我們要知道,在WebViewJavascriptBridge中,交互的方式只有兩種:send 和 callHandle,JS和OC都有這兩個方法,所以對應的四種關系是:

以上表中的對應關系的解讀是,例如第一條:在JS中如果調用了bridge.send(),那么將觸發OC端_bridge初始化方法中的回調。

同理,第二條,在JS中調用了bridge.callHandler('testJavascriptHandler'),它將觸發OC端注冊的同名方法:

1 bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {

2? ? ? ? log('ObjC called testJavascriptHandler with', data)

3? ? ? ? var responseData = { 'Javascript Says':'Right back atcha!' }

4? ? ? ? log('JS responding with', responseData)

5? ? ? ? responseCallback(responseData)

6 })

了解了使用規則,下面來看看在我們這個實際需求中應用的整體思路:

—— 1 ——

首先,我們要做的第一步是替換獲取的HTML文本中默認的src,以避免其會自動加載圖片。

1 NSString *_content = [contentstring stringByReplacingOccurrencesOfString:@"src" withString:@"esrc"];

—— 2 ——

因為我們獲取的只是HTML的body部分,因此我們需要自己書寫完整的HTML。

我們讓《body onload="onLoaded()"》的時候去調用JS中的 onLoaded()函數。在這個函數中我們遍歷所有img標簽的 esrc,保存為一個數組返回給 OC 端,讓native端去下載這些圖片。

復制代碼

復制代碼

1 function onLoaded() {

2? ? connectWebViewJavascriptBridge(function(bridge) {

3? ? ? ? var allImage = document.querySelectorAll("img");

4? ? ? ? allImage = Array.prototype.slice.call(allImage, 0);

5? ? ? ? var imageUrlsArray = new Array();

6? ? ? ? allImage.forEach(function(image) {

7? ? ? ? ? ? var esrc = image.getAttribute("esrc");

8? ? ? ? ? ? var newLength = imageUrlsArray.push(esrc);

9? ? ? ? });

10? ? ? ? bridge.send(imageUrlsArray);

11? ? });

12 }

復制代碼

復制代碼

—— 3 ——

bridge.send 會觸發WebViewJavascriptBridge初始化方法 + (instancetype)bridgeForWebView:(WVJB_WEBVIEW_TYPE*)webView webViewDelegate:(WVJB_WEBVIEW_DELEGATE_TYPE*)webViewDelegate handler:(WVJBHandler)handler; 中的handler,我們在handler的block中下載所有圖片。并且把下載完的圖片在cache中的地址返回個JS。

復制代碼

復制代碼

1 #pragma mark -- 下載全部圖片

2 -(void)downloadAllImagesInNative:(NSArray *)imageUrls{

3? ? SDWebImageManager *manager = [SDWebImageManager sharedManager];

4? ? //初始化一個置空元素數組

5? ? _allImagesOfThisArticle = [NSMutableArray arrayWithCapacity:imageUrls.count];//本地的一個用于保存所有圖片的數組

6? ? for (NSUInteger i = 0; i < imageUrls.count-1; i++) {

7? ? ? ? [_allImagesOfThisArticle addObject:[NSNull null]];

8? ? }

9? ? for (NSUInteger i = 0; i < imageUrls.count-1; i++) {

10? ? ? ? NSString *_url = imageUrls[i];

11? ? ? ? [manager downloadImageWithURL:[NSURL URLWithString:_url] options:SDWebImageHighPriority progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {

12? ? ? ? ? ? if (image) {

13? ? dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

14? ? ? ? ? ? ? ? ? ? NSString *imgB64 = [UIImageJPEGRepresentation(image, 1.0) base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];

15? ? ? ? ? ? ? ? ? ? //把圖片在磁盤中的地址傳回給JS

16? ? ? ? ? ? ? ? ? ? NSString *key = [manager cacheKeyForURL:imageURL];

17? ? ? ? ? ? ? ? ? ? NSString *source = [NSString stringWithFormat:@"data:image/png;base64,%@", imgB64];

18? ? ? ? ? ? ? ? ? ? [_bridge callHandler:@"imagesDownloadComplete" data:@[key,source]];

19? ? ? ? ? ? ? ? });

20? ? ? ? ? ? }

21? ? ? ? }];

22? ? }

23 }

復制代碼

復制代碼

—— 4 ——

[_bridge callHandler:@"imagesDownloadComplete" data:@[key,source]] 會觸發JS中的 function imagesDownloadComplete()。在這個函數中遍歷所有img標簽,把傳過來的圖片地址賦值給img的src。

復制代碼

復制代碼

1 function imagesDownloadComplete(pOldUrl, pNewUrl) {

2? ? var allImage = document.querySelectorAll("img");

3? ? allImage = Array.prototype.slice.call(allImage, 0);

4? ? allImage.forEach(function(image) {

5? ? ? ? if (image.getAttribute("esrc") == pOldUrl || image.getAttribute("esrc") == decodeURIComponent(pOldUrl)) {

6? ? ? ? ? ? image.src = pNewUrl;

7? ? ? ? }

8? ? });

9 }

復制代碼

復制代碼

至此,通過WebViewJavascriptBridge處理UIWebView和JS交互實現本地處理網頁圖片的下載操作就基本完成了。這個例子展現了一個完整的過程,基本涉及了JS和OC的各種交互包括OC調用JS、JS調用OC等。如果你有其它的業務需求,也基本按照這個流程就可以依樣畫葫蘆了,唯一不同的也就是業務邏輯了。

下面我再舉一個例子。也是出現在我的業務需求里的,就是點擊網頁上的圖片,圖片會以Zoom-out的動畫放大,左右滑動可以查看其它圖片,同時還需要雙擊放大查看、保存圖片等功能。 類似這樣:

乍一看,我們點擊的是一張網頁上的圖片,怎么可能讓這張圖片單獨跳出來?而且還能左右滑動顯示其它圖片?

首先我們還是需要去改造網絡獲取的那段HTML文本,正則匹配出 img esrc=http://....,加上onClick事件,綁定一個JS的方法,并把這個esrc作為參數傳入這個綁定的方法中。

//正則替換

NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(《img[^》]+esrc=\")(\\S+)\"" options:0 error:nil];

result = [regex stringByReplacingMatchesInString:newContent options:0 range:NSMakeRange(0, newContent.length) withTemplate:@"《img esrc=\"$2\" onClick=\"javascript:onImageClick('$2')\""];

JS中onImageClick()函數。這個函數的主要任務是:獲取點擊圖片的在所有圖片中的編號以及在當前屏幕中的位置。并把這些信息返回給OC。

復制代碼

復制代碼

1 function onImageClick(picUrl){

2? ? connectWebViewJavascriptBridge(function(bridge) {

3? ? ? ? var allImage = document.querySelectorAll("p img[esrc]");

4? ? ? ? allImage = Array.prototype.slice.call(allImage, 0);

5? ? ? ? var urls = new Array();

6? ? ? ? var index = -1;

7? ? ? ? var x = 0;

8? ? ? ? var y = 0;

9? ? ? ? var width = 0;

10? ? ? ? var height = 0;

11? ? ? ? allImage.forEach(function(image) {

12? ? ? ? ? ? var imgUrl = image.getAttribute("esrc");

13? ? ? ? ? ? var newLength = urls.push(imgUrl);

14? ? ? ? ? ? if(imgUrl == picUrl || imgUrl == decodeURIComponent(picUrl)){

15? ? ? ? ? ? ? ? index = newLength-1;

16? ? ? ? ? ? ? ? x = image.getBoundingClientRect().left;

17? ? ? ? ? ? ? ? y = image.getBoundingClientRect().top;

18? ? ? ? ? ? ? ? x = x + document.documentElement.scrollLeft;

19? ? ? ? ? ? ? ? y = y + document.documentElement.scrollTop;

20? ? ? ? ? ? ? ? width = image.width;

21? ? ? ? ? ? ? ? height = image.height;

22? ? ? ? ? ? ? ? console.log("x:"+x +";y:" + y+";width:"+image.width +";height:"+image.height);

23? ? ? ? ? ? }

24? ? ? ? });

25? ? ? ? console.log("檢測到點擊");

26? ? ? ? bridge.callHandler('imageDidClicked', {'index':index,'x':x,'y':y,'width':width,'height':height}, function(response) {

27? ? ? ? ? ? console.log("JS已經發出imgurl和index,同時收到回調,說明OC已經收到數據");

28? ? ? ? });

29? ? });

30 }

復制代碼

復制代碼

bridge.callHandler 會觸發OC中的 [_bridge registerHandler:@"imageDidClicked" handler:^(id data, WVJBResponseCallback responseCallback){}]。我們可以再handler中獲得JS傳過來的點擊圖片在所有圖片中的編號,以及點擊圖片在當前圖片中的空間位置。要實現點擊圖片Zoom-out的效果,我們要善于「作弊」。網頁中的圖片固然不能「跳」出來放大,但我們可以根據JS傳回來的x、y、width、height這些位置信息自己創建一個UIImageView,image和當前點擊圖片一致,設置透明度為0,add到UIWebView上面。并通過IDMPhotoBrowser 這個開源庫實現圖片瀏覽。

復制代碼

復制代碼

1? [_bridge registerHandler:@"imageDidClicked" handler:^(id data, WVJBResponseCallback responseCallback) {

2? ? ? ? NSInteger index = [[data objectForKey:@"index"] integerValue];

3? ? ? ? CGFloat originX = [[data objectForKey:@"x"] floatValue];

4? ? ? ? CGFloat originY = [[data objectForKey:@"y"] floatValue];

5? ? ? ? CGFloat width? = [[data objectForKey:@"width"] floatValue];

6? ? ? ? CGFloat height? = [[data objectForKey:@"height"] floatValue];

7? ? ? ? tappedImageView.alpha = 0;

8? ? ? ? tappedImageView.frame = CGRectMake(originX, originY, width, height);

9? ? ? ? tappedImageView.image = _allImagesOfThisArticle[index];//_allImagesOfThisArticle是一個本地數組用來存放所有圖片

10? ? ? ? NSLog(@"OC已經收到JS的imageDidClicked了: %@", data);

11? ? ? ? responseCallback(@"OC已經收到JS的imageDidClicked了");

12? ? ? ? //點擊放大圖片

13? ? ? ? [self presentPhotosBrowserWithInitialPage:index animatedFromView:tappedImageView];

14? ? }];

復制代碼

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,755評論 0 9
  • 第5章 引用類型(返回首頁) 本章內容 使用對象 創建并操作數組 理解基本的JavaScript類型 使用基本類型...
    大學一百閱讀 3,263評論 0 4
  • 事情的起因還是因為項目需求驅動。折騰了兩天,由于之前沒有UIWebView與JS交互的經歷,并且覺得這次在功能上有...
    CocoLeo閱讀 721評論 0 3
  • 在我童年時,家里還沒拆遷,老屋子是個樓房,上下兩層,臥室在二樓,一樓是客堂間,廚房和天井。天井面積不算很大,足以容...
    韋羋閱讀 745評論 0 1
  • 理想很豐滿,現實很骨感。每當看到這句話,我都非常一廂情愿的想,如果是指身材,骨感也很好。 骨感的現實成了很...
    lilycoffee閱讀 344評論 1 1