1. 程式人生 > >網易新聞詳情頁排版實現思路

網易新聞詳情頁排版實現思路

  不論是哪一家新聞app的新聞詳情頁都有大量的圖片和片斷性的文字,一直很好奇它排版的具體實現方式,構想過一些辦法(富文字之類)發現都不是明智的方法,所以決定研究一下。
  查閱資料得知新聞頁面大都是用UIWebView載入html來完成。參考博文網易新聞客戶端iOS版本中新聞詳情頁(UIWebView)技術實現的分析探討的內容以及作者的demo我也實現了一遍。

涉及的技術點:

  * html模板引擎的使用,我使用的GRMustache
  * JS與OC之間的通訊,可以使用WebViewJavascriptBridge
  * SDWebImage的使用,這裡指的主要是指下載圖片,返回已經下載完成的圖片在本地的地址。
  ps:需要一點點html和js語法上的知識,如果不懂我覺得靠google/baidu也是能夠搞定的。
  

效果

  原諒我偷懶,沒有自己去抓資料,是參照網易新聞客戶端iOS版本中新聞詳情頁(UIWebView)技術實現的分析探討這篇博文裡作者抓取的連結來完成demo,我這篇其實就是這個博文的稍微囉嗦一點的版本。我的大部分思路都是來自這篇文章。

主要實現

1.WebViewJavascriptBridge的初始化

- (void)initBridge {
    // 開啟日誌
    [WebViewJavascriptBridge enableLogging];

    //設定給哪個webView建立js與oc通訊的橋樑
    self.bridge = [WebViewJavascriptBridge bridgeForWebView:self
.webView]; //如果需要實現UIWebViewDelegate可以設定代理 [self.bridge setWebViewDelegate:self]; //註冊 用於js主動呼叫oc [self.bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) { NSLog(@"我是js主動呼叫oc後的輸出"); }]; //註冊圖片點選事件 __weak typeof(self)weakSelf = self
; [self.bridge registerHandler:@"tapImage" handler:^(id data, WVJBResponseCallback responseCallback) { //點選圖片的index NSLog(@"=======%@=========", data); NSString *index = (NSString *)data; //初始化圖片瀏覽器 [weakSelf browseImages:index.integerValue]; }]; //oc主動呼叫js [self.bridge callHandler:@"testJavascriptHandler" data:nil responseCallback:^(id responseData) { NSLog(@"我是oc主動呼叫js後的輸出"); }]; }

  關於WebViewJavascriptBridge的使用,一開始看著回撥的使用可能會有點暈,特別是之前的版本中,呼叫可以通過sendcallHandler兩種方法,最近的版本中好像已經只採用callHandler這一種方法來進行呼叫了,簡化了理解。
  其實簡單來說就是一個註冊&呼叫的關係。如果js需要呼叫oc的程式碼,那麼js是主動方,用callHandler方法,即呼叫的字面意思。而oc是被呼叫的一方,需要註冊一個方法用於被呼叫,即用registerHandler方法,反過來oc呼叫js是一樣的。
  可以通過UIWebView與JS的深度互動這篇文章進行理解。不過最近的版本中已經沒有再採用send的方法來呼叫了。

2.網路請求  

- (void)httpRequest {
    self.detailID = @"AQ4RPLHG00964LQ9";//多張圖片
    NSMutableString *urlStr = [NSMutableString stringWithString:@"http://c.m.163.com/nc/article/xukunhenwuliao/full.html"];
    [urlStr replaceOccurrencesOfString:@"xukunhenwuliao" withString:_detailID options:NSCaseInsensitiveSearch range:[urlStr rangeOfString:@"xukunhenwuliao"]];

    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    __weak typeof(self)weakSelf = self;
    [manager GET:urlStr parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        DataInfo *model = [DataInfo mj_objectWithKeyValues:[responseObject objectForKey:self.detailID]];
        NSLog(@"請求成功");
        [weakSelf handleData:model];

    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull   error) {
        NSLog(@"%@",error);  //這裡列印錯誤資訊
    }];
}

  這裡的使用應該沒有問題,就是利用AFN進行網路請求,請求成功後對返回的json進行處理。

3.返回資料的簡單分析
  * 返回的欄位

{
    AQ4RPLHG00964LQ9: {
        body: "<p></p>",
        users: [ ],
        ydbaike: [ ],
        replyCount: 12550,
        link: [ ],
        img: [],
        votes: [],
        shareLink: "https://c.m.163.com/news/a/ AQ4RPLHG00964LQ9.html?spss=newsapp&spsw=1",
            digest: "",
        topiclist_news: [ ],
        dkeys: "輕鬆一刻",
        topiclist: [ ],
        docid: "AQ4RPLHG00964LQ9",
        picnews: true,
        title: "每日輕鬆一刻(5月21日午間)",
        tid: "",
        template: "special",
        threadVote: 12,
        rewards: [],
        threadAgainst: 5,
        boboList: [ ],
        replyBoard: "3g_bbs",
        source: "網易新媒體",
        hasNext: false,
        voicecomment: "off",
        ptime: "2015-05-21 11:14:01"
        }
}

  這裡是返回的全部欄位,具體的欄位值我進行了省略,因為太長了。下面著重看bodyimg欄位。
  
  * body

<p>說好的,521繼續虐狗!來點刺激的!</p>
<!--IMG#0-->
<p>什麼小年輕秀恩愛都俗透了,不是經常被罵分得快麼,要玩就玩大的,彼此都老了是種啥感覺?</p>
<p>這對20多歲的情侶被化妝成507090歲的模樣,老溼的小心臟。。。</p>
<!--IMG#1-->
<!--IMG#2-->
<!--IMG#3--><p>一夜變老,這酸爽,比熬了幾個大黑夜還見效!</p>

  這裡我只是選取了部分body的值,但是已經可以發現規律了,有大量的<!--IMG#?-->這種形式的文字摻雜在其中,這其實就是圖片的佔位符,每個佔位符都不相同,即對應著不同的圖片。下面看img的欄位值就能理解了。
  
* img

img: [
    {
    ref: "<!--IMG#0-->",
    pixel: "550*767",
    alt: "",
    src: "http://img5.cache.netease.com/m/2015/5/21/20150521105913d3770_550.jpg"
    },
    {
    ref: "<!--IMG#1-->",
    pixel: "356*201",
    alt: "",
    src: "http://img3.cache.netease.com/m/2015/5/21/201505211111290babf.gif"
    },
    ......
]

  可以看出來,img陣列每個元素中都有一個ref欄位,這個值是與上方body中的一一對應的,圖片的尺寸、url都在img的元素中給出了,下一步就是拿到通過img元素給出的圖片資訊來組成html文字替換body中圖片的佔位符。

4.body中圖片佔位符以html格式文字來替換

- (NSMutableString *)handleImageInNews:(DataInfo *)data {
    NSMutableString *bodyStr = [data.body mutableCopy];

    [data.img enumerateObjectsUsingBlock:^(ImageInfo *info, NSUInteger idx, BOOL * _Nonnull stop) {
        NSRange range = [bodyStr rangeOfString:info.ref];
        NSArray *wh = [info.pixel componentsSeparatedByString:@"*"];
        CGFloat width = [[wh objectAtIndex:0] floatValue];
        CGFloat height = [[wh objectAtIndex:1] floatValue];

        //佔位圖
        NSString *loadingImg = [[NSBundle mainBundle] pathForResource:@"loading" ofType:@"png"];
        NSString *imageStr = [NSString stringWithFormat:@"<p style = 'text-align:center'><img onclick = 'didTappedImage(%lu);' src = %@ id = '%@' width = '%.0f' height = '%.0f' hspace='0.0' vspace ='5' style ='width:60%%;height:60%%;' /></p>", (unsigned long)idx, loadingImg, info.src, width, height];
        [bodyStr replaceOccurrencesOfString:info.ref withString:imageStr options:NSCaseInsensitiveSearch range:range];
    }];

    [self getImageFromDownloaderOrDiskByImageUrlArray:data.img];

    return bodyStr;
}

  通過圖片的ref欄位,一一用html格式的文字來替換body中對應的圖片的佔位文字,如<!--IMG#0-->將被替換為如下:

<p style = 'text-align:center'><img onclick = 'didTappedImage(0);' src = /Users/yiban/Library/Developer/CoreSimulator/Devices/25D5A6D1-94EB-4C29-8FF4-3158CC846935/data/Containers/Bundle/Application/E67999A2-27B2-4C4D-A137-1D2B56241B61/WebHtmlLoad.app/loading.png id = 'http://img5.cache.netease.com/m/2015/5/21/20150521105913d3770_550.jpg' width = '550' height = '767' hspace='0.0' vspace ='5' style ='width:80%;height:80%;' /></p>

  這裡有一個loadingImg,是本地一張圖片,用來作為圖片未下載完成時的loading圖。新聞中的圖片根據url下載完成後找到該圖片在本地的地址替換掉html文字中src的值,即替換掉佔位圖的地址。

5.拼接html格式的標題

- (NSMutableString *)handleNewsTitle:(DataInfo *)data {
    NSMutableString *htmlTitleStr = [NSMutableString stringWithString:@"<style type='text/css'> p.thicker{font-weight: 900}p.light{font-weight: 0}p{font-size: 108%}h2 {font-size: 120%}h3 {font-size: 80%}</style> <h2 class = 'thicker'>{{title}}</h2><h3>{{source}}{{ptime}}</h3>"];
    return [[GRMustacheTemplate renderObject:@{@"title" : data.title, @"source" : data.source, @"ptime" : data.ptime} fromString:htmlTitleStr error:NULL] mutableCopy];
}

  這裡的html文字中出現了雙大括號{{xxx}}的寫法,其實這是html模板引擎GRMustache的語法。我這裡只使用了它最基本的一個用法,用{{}}包住你想要替換的佔位文字,然後在一個字典內用相同的佔位文字作為key給html賦值。模板引擎的使用可以很優雅的實現這種文字值的替換填充,如果不使用模板引擎上面的程式碼如下:

- (NSMutableString *)handleNewsTitle:(DataInfo *)data {
    NSMutableString *htmlTitleStr = [NSMutableString stringWithString:@"<style type='text/css'> p.thicker{font-weight: 900}p.light{font-weight: 0}p{font-size: 108%}h2 {font-size: 120%}h3 {font-size: 80%}</style> <h2 class = 'thicker'>title</h2><h3>source ptime </h3>"];
    [htmlTitleStr replaceOccurrencesOfString:@"title" withString:data.title options:NSCaseInsensitiveSearch range:[htmlTitleStr rangeOfString:@"title"]];
    [htmlTitleStr replaceOccurrencesOfString:@"source" withString:data.source options:NSCaseInsensitiveSearch range:[htmlTitleStr rangeOfString:@"source"]];
    [htmlTitleStr replaceOccurrencesOfString:@"ptime" withString:data.ptime options:NSCaseInsensitiveSearch range:[htmlTitleStr rangeOfString:@"ptime"]];

    return htmlTitleStr;
}

  有很多重複的replaceOccurrencesOfString,如果需要替換的文字很多時,感覺是在做大量的重複工作,所以這種時候就適用模板引擎,能夠更優雅的實現替換值。

6.圖片快取

- (void)getImageFromDownloaderOrDiskByImageUrlArray:(NSArray *)imageArray {
    SDWebImageManager *imageManager = [SDWebImageManager sharedManager];
    self.imagesArr = [NSMutableArray array];
    __weak typeof(self)weakSelf = self;
    for (ImageInfo *info in imageArray) {
        NSURL *imageUrl = [NSURL URLWithString:info.src];
        [self.imagesArr addObject:imageUrl];
        [imageManager diskImageExistsForURL:imageUrl completion:^(BOOL isInCache) {
            isInCache ? [weakSelf handleExistCache:imageUrl] : [weakSelf handleNotExistCache:imageUrl];
        }];
    }
}

//已經有圖片快取
- (void)handleExistCache:(NSURL *)imageUrl {
    SDWebImageManager *imageManager = [SDWebImageManager sharedManager];
    NSString *cacheKey = [imageManager cacheKeyForURL:imageUrl];
    NSString *imagePath = [imageManager.imageCache defaultCachePathForKey:cacheKey];

    NSString *sendData = [NSString stringWithFormat:@"replaceimage%@,%@", imageUrl.absoluteString, imagePath];
    [self.bridge callHandler:@"replaceImage" data:sendData responseCallback:^(id responseData) {
        NSLog(@"%@", responseData);
    }];
}

//本地沒有圖片快取
- (void)handleNotExistCache:(NSURL *)imageUrl {
    SDWebImageManager *imageManager = [SDWebImageManager sharedManager];
    __weak typeof(self)weakSelf = self;

    [imageManager downloadImageWithURL:imageUrl options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        if (image && finished) {
            NSLog(@"下載成功");
            [weakSelf handleExistCache:imageUrl];
        } else {
            NSLog(@"圖片下載失敗");
        }
    }];
}

  這裡涉及到的主要技術點就是圖片的快取、快取的本地路徑獲取、用WebViewJavascriptBridge向js傳送訊息。圖片這一塊就不再贅述了,大家應該都能掌握,那就稍微解釋一下callHandler
  程式成功下載並獲得圖片的快取路徑是在oc程式碼裡,我們希望獲取的圖片能在html程式碼里加載,這就需要oc告訴js圖片的路徑,所以是oc主動呼叫js程式碼,用callHandler方法。replaceImage是用來標識一個方法,呼叫js裡用replaceImage註冊的方法。sendData是oc想要傳給js的引數,引數的值是repalceImage+圖片的url+ , +圖片的本地路徑。在第四點中我們用html格式文字來替換圖片佔位符時,html文字的img是用url作為id的,所以這裡傳入圖片的url是為了能在js中根據這個url獲取到指定的圖片img,imgsrc真正的值是這裡傳過去的本地路徑。

7.js程式碼

function didTappedImage(index) {
    setupWebViewJavascriptBridge(function(bridge) {
                                 bridge.callHandler('tapImage', index,
                                                    function(response) {})
                                 })
}

bridge.registerHandler('replaceImage', function(data, responseCallback) {
                                                        if (data.match("replaceimage")) {
                                                            var index = data.indexOf(",")
                                                            var messageReplace = data.substring(0, index)
                                                            var messagePath = data.substring(index+1)
                                                            messageReplace = messageReplace.replace(/replaceimage/, "")
                                                            element = document.getElementById(messageReplace)
                                                            if (element.src.match("loading")) {
                                                                responseCallback(messagePath)
                                                                element.src = messagePath
                                                            }
                                                        }
                                                        })

  js除去語法層面,最主要的就是了解WebViewJavascriptBridge的用法。圖片路徑的替換,看程式碼就能理解了,一共也沒幾句呢。圖片的點選事件,在圖片的html程式碼裡有寫到onclick = 'didTappedImage(%lu);',引數是圖片的序號,用於圖片瀏覽器能定位到當前點選的圖片。這裡需要在js裡有一個方法,用來供點選事件的時候呼叫,這個方法又要用來呼叫oc程式碼,所以是html裡的圖片點選觸發一個事件呼叫js函式,js函式需要告訴oc發生了這件事,是js主動呼叫oc程式碼,js裡用bridge.callHandler。需要注意的是,因為這是oc和js通訊的程式碼,必須寫在setupWebViewJavascriptBridgecallback裡,不然呼叫不起作,屬於WebViewJavascriptBridge的一些用法。
  

寫在最後

  寫這篇demo不是一帆風順的,遇到一個神坑,折騰了好久。我用CocoaPods下載WebViewJavascriptBridge, 然後去它的git上下載demo,想看看demo的使用,然後拷貝demo裡的html檔案到自己專案裡修改功能,結果死活沒法讓oc和js互相呼叫,然而各種檢查發現都沒有錯。最後,我把demo裡WebViewJavascriptBridge的資料夾拖過來,刪掉了CocoaPods下載的版本,於是世界和平。。。
  程式碼我已經是各種精簡了,命名什麼的也盡力準確,希望大家理解起來能比較順利。
  老規矩,上地址: git
  
  

相關推薦

新聞詳情排版實現思路

  不論是哪一家新聞app的新聞詳情頁都有大量的圖片和片斷性的文字,一直很好奇它排版的具體實現方式,構想過一些辦法(富文字之類)發現都不是明智的方法,所以決定研究一下。   查閱資料得知新聞頁面大都是用UIWebView載入html來完成。參考博文網易新聞客戶

Android實現仿選項卡動態滑動效果

本文會實現一個類似網易新聞(不說網易新聞大家可能不知道大概是什麼樣子)點選超多選項卡,選項卡動態滑動的效果。  首先來看看佈局,就是用HorizontalScrollView控制元件來實現滑動的效果,裡面包含了一個佈局。 <code class="hljs xml has-numbering" st

2017最新淘寶高轉化詳情排版技巧(轉載)

log 產品 風格 span 有用 img 信息 核心 屬於 詳情頁,首先我們要明白一件事情,詳情頁是什麽?是幹什麽的?平時我們在線下門店或商場買東西的時候,我們能直觀的看到商品,可以摸到商品的材質,還可以聽到銷售人員的講解。而淘寶上的商品,用戶只能靠眼睛去看,最終下不下單

Java Web分顯示實現思路

實現效果 一.需求描述 從資料庫中將所有資料查詢出來,分頁顯示在前端頁面上,每頁顯示若干條資料,並實現"首頁","上一頁","下一頁","尾頁","跳轉至指定頁碼","顯示當前頁碼"等功能   二.實現思路  我的思路是將當前頁碼的值作為引數傳給servlet

iOS開發-UIWebView新增頭部與尾部控制元件 && 仿iOS 今日頭條新聞詳情結構實現

在app開發中我們經常會遇到在內容詳細頁中介面元素比較複雜,或者格式不確定這種情況,通常我們會利用UIWebView來載入html來處理這樣的事件,因為這樣不僅簡單而且可控性更好,我們不會再因為內容格式的改變,再去苦逼的改程式碼,一層層的解析資料,在苦苦的等待稽核,但

知乎日報詳情實現(整合webView)

詳情頁的實現 知乎日報的文章詳情頁是使用一個 WebView 顯示內容的。遺憾的是,React Native 官方在 Android 上並沒有提供 WebView 的支援。好在 React Native 很容易整合原生的元件: Native UI Component

案例:用vue開發雲音樂(已實現線上播放和下載)

效果如圖: 完整程式碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content

Qt-雲音樂介面實現-7 訊息中心實現,主要是QListWidget 自定義Item 和QTabwidget使用

最近寫的有點煩躁, 感覺內容真的很多!很多!很多。目前真的想知道網易官方在出這款產品是,用了多少人和多長時間。今天寫的這個訊息中心,有點糙,只是原理實現了沒有完全複製過來,心裡有團火,不想寫了。看下效果吧其實這個訊息中心的內容到時很簡單,最底層一個Qtabwidgte,構成@

ScrollView仿微博詳情——輕鬆實現標題欄懸浮、漸變及Fragment內容切換

 作為一名熱愛學習的Android開發工程si,刷微博的時候居然還想著技術呢,覺得自己也是夠夠了........哈哈哈 進入今天的正題,微博主頁大家肯定是看過的,先看一下微博的效果。(小提示:該Demo是採用kotlin語言編寫的,需要配置Kotlin開發環境哦!)微博的效果

自定義外掛實現雲音樂首圖片輪播

編寫html介面 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>網易雲音樂

[iOS開發]關於仿新聞中詳細圖文混排功能的實現

{"B4A39DDB00964LQ9":{"body":"   給你們講一個恐怖的故事:聽說從今天開始2015年只剩下100天了!<\/p>   2015年快過去了,年初定下的目標都達成了嗎?時間總是不知不覺匆匆地過去,不留下一絲痕跡。<\/p>   俗語有云:生命很短,我們真的沒必

關於仿新聞中詳細圖文混排功能的實現

{"B4A39DDB00964LQ9":{"body":"   給你們講一個恐怖的故事:聽說從今天開始2015年只剩下100天了!<\/p>   2015年快過去了,年初定下的目標都達成了嗎?時間總是不知不覺匆匆地過去,不留下一絲痕跡。<\/p>   俗語有云:生命很短,我們真的沒必

仿京東、天貓app的商品詳情的布局架構, 以及功能實現

enter layout 顯示 效果 寫上 idt theme brush 2.2.0 一、介紹 這個類是繼承自ImageView的,所以對於這個控件我們可以使用ImageView的所有屬性 二、使用準備, 在as 的 build.grade文件中寫上 compile ‘

Android ScrollView滾動實現大眾點評、雲音樂評論懸停效果

ins schema bar 音樂 layout mage for bin andro 今天聽著網易雲音樂,寫著代碼,真是爽翻了。 http://blog.csdn.net/linshijun33/article/details/47910833 網

微信 HTML5 實現列表詳情無刷新返回 seesionStorage

list tar 尋址 ron string 開發 lan 情況 detail 最近在最微信端開發,遇到了一個比較有意思的問題。 1:商品分頁列表頁 2:商品詳情頁 需求: 實現當在第N頁的列表頁,點擊ID=Num的商品詳情頁,跳轉到詳情頁後,再點擊返回按鈕,依舊返回到

iOS雲音樂首源碼、動畫引擎源碼等

圖像 自帶 集成 ref ole hololens demo 拖拽 基礎 iOS精選源碼 自己維護的框架, 超級多功能 圖片選擇SDK:支持多選,相冊選擇,預覽,網絡圖預覽 一款可以簡單實現長按拖拽重排的 UICe

用RotateDrawable實現雲音樂唱片機效果

image 不難 線程 們的 progress ogr icon 什麽 als 有一段時間沒有更新文章了,記得上一篇文章講的是《用ClipDrawable實現音頻錄制麥克風講話效果》,用戶反響也都還不錯,自己也是深受鼓勵。事實上從那之後就一直想寫一篇

vue實現淘寶商品詳情屬性選擇功能

line pan func sel eth AD 圖片 [1] urn 方法一是自己想出來的,方法二來自忘記哪裏看到的了 不知道是不是你要的效果: 方法一:利用input[type="radio"] css代碼: 1 input { 2

ccnp大型園區實現思路解析

高可用 冗余 互載均衡 企業 實戰 園區網絡規劃拓撲圖如下: 設計思路:區分出二層三層 (哪些地方要用到二層技術,哪些地方要用到三層技術) 二層技術(vlan vtp 端口聚合 stp ) 三層(dhcp(七層) *** ospf 默認路由 nat 端口映射(七層) ) 安全(acl 各

雲歌詞解析(配合audio標簽實現本地歌曲播放,歌詞同步)

極限 telling image 更多 ger 12.1 src say aud 先看下效果 中文歌曲 英文歌曲(如果有翻譯的中文給回返回出去) 韓文歌曲 來看下解析歌詞的類 class Lyric { constructor(data) {