【iOS】仿知乎日報,RxSwift-Part2-詳情頁的搭建
- 2020 年 3 月 30 日
- 筆記
前言
在上一篇,我們搭建了首頁。而這篇,我們將開始搭建話題詳情頁。
分析
還是先來看下演示gif

詳情頁.gif
再結合話題詳情的接口分析 http://news-at.zhihu.com/api/4/news/9649565。具體的json格式如下:
{ "body": "<div class="main-wrap content-wrap">n<div class="headline">nn<div class="img-place-holder"></div>nnnn</div>nn<div class="content-inner">nnnnn<div class="question">n<h2 class="question-title">機會成本是否有「時效性」?</h2>nn<div class="answer">nn<div class="meta">n<img class="avatar" src="http://pic4.zhimg.com/b1ccdc223_is.jpg">n<span class="author">Kallas,</span><span class="bio">Penn State Econ Ph.D. Student</span>n</div>nn<div class="content">n<p>是的,機會成本是一個非常簡化的概念,題主敏銳的發現了這個問題。機會成本特別適合<strong>靜態、有限選擇、風險因素不重要</strong>時候的分析,但是當存在風險、選擇無限、動態問題的時候,機會成本這一概念就顯得過於簡單了。</p>rn<p>機會成本遺漏了<strong>風險結構</strong>,兩塊錢可以買一瓶水,也可以買彩票;可以買獎金 500 萬但是中獎率千萬分之一的大彩票,也可以買獎金 10 塊但是中間率高很多的小彩票。買大彩票還是小彩票不光取決於機會成本(以期望收益計算),也取決於個人的風險偏好。技術性地講,機會成本特別適用一階隨機佔優時候的比較,但是當風險是主要因素的時候就不太適用。</p>rn<p>而且兩塊錢買一瓶水 vs 兩塊錢買張彩票,和 200 塊錢買 100 瓶水 vs 100 張彩票又不一樣。我可以花其中的 180 塊錢去買水,剩下的錢買彩票,這樣的選擇有非常多種。這樣的選擇有非常多。我們當然依然可以列出所有的選項,然後從中挑選一個最偏好的方案。但是更方便的辦法可能是用<strong>邊際效用</strong>來描述這個新的選擇問題。</p>rn<p>題主所說的時效性,我舉另一個例子。比如題主在考前糾結是看電影還是複習。看電影要花 30 塊錢買票,還要搭上兩小時的時間,這時候的機會成本就是 30 塊錢 + 兩小時的複習量(同時也可以思考複習的機會成本是啥)。但是如果看了一半發現電影很無聊,考慮要不要回去複習,那麼這時候的機會成本就是一小時的複習量。而回去複習的機會成本就是剩下一小時的愉悅 + 可能的彩蛋。(看,又有「可能性」的問題)。可以看到機會成本是隨着時間不斷變化的。如果題主在看電影的每時每刻都在做這樣的比較,那麼用機會成本來刻畫選擇就會變得非常複雜,一個更好的選擇是做成動態規劃問題。</p>rn<p>曼昆一開始就介紹機會成本的概念是因為它非常簡單、符合直覺,並且生活中非常多的問題確實也是可以用機會成本的概念思考的。我上面說的有些名詞不理解並無所謂,後來慢慢都會知道的。題主剛接觸經濟學就能有這樣反思概念的意識非常好,經濟學就是這樣不斷在概念和反思概念中發展起來的。</p>n</div>n</div>nnn<div class="view-more"><a href="http://www.zhihu.com/question/66457929">查看知乎討論<span class="js-question-holder"></span></a></div>nn</div>nnn</div>n</div>", "image_source": "Public Domain", "title": "考前糾結是看電影還是複習?這你可牽扯到經濟學問題了", "image": "https://pic2.zhimg.com/v2-003879862c9104f540b05001938983fd.jpg", "share_url": "http://daily.zhihu.com/story/9649565", "js": [], "ga_prefix": "101309", "images": [ "https://pic3.zhimg.com/v2-158fb865f361b059aedfcc65e25bd06a.jpg" ], "type": 0, "id": 9649565, "css": [ "http://news-at.zhihu.com/css/news_qa.auto.css?v=4b3e3" ] }
不難發現,返回的數據是返回HTML的Body內容,而CSS樣式則讀取css字段。那麼主題內容需要我們「拼出」一個HTML格式的字符串,然後用webView進行加載。而頭部的圖片(image),文字(title),圖片來源(image_source)需要我們自己布局及加載。
要點解析
1、自定義WKWebView
按以上的分析,我們需要自定義一個WKWebView,頭部需要插入圖片,標題Label等元素,還要在該webView的頭部和底部添加上下加載的提示語。由於我們在WKWebView的底部添加提示語「加載下一篇」,所以我們需要獲得該webview的contentSize。
由於WKWebView不能通過scrollView.contentSize直接獲取內容告訴,所以在webView加載完畢時,調用了js語句,獲取其內容高度:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { webView.evaluateJavaScript("document.body.scrollHeight") { (result, error) in if let height = result as? CGFloat { self.nextLabel.frame.origin.y = height + 50 } } }
2、拼接HTML
上面也說了,接口返回的只有HTML的Body內容,以及CSS連接,所以我們需要額外添加<HTML></HTML>等元素,使之合乎規範。
具體拼接方式如下:
/// 加載HTML網頁 fileprivate func loadHTML(model: MPStoryDetailModel) { guard let css = model.css, let body = model.body else { return } var html = "<html>" html += "<head>" css.forEach { html += "<link rel="stylesheet" href=($0)>" } html += "<style>img{max-width:320px !important;}</style>" html += "<body>" html += body html += "</body>" html += "</head>" html += "</html>" self.loadHTMLString(html, baseURL: nil) }
3、內容自適應
WKWebView的內容自適應比UIWebView稍微麻煩一點,我是在WKWebView創建時,設置了js語句
init() { // 設置內容自適應 let js = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);" let wkUserScript = WKUserScript(source: js, injectionTime: .atDocumentEnd, forMainFrameOnly: true) let config = WKWebViewConfiguration() let wkUControl = WKUserContentController() wkUControl.addUserScript(wkUserScript) config.userContentController = wkUControl super.init(frame: CGRect.zero, configuration: config) }
4、上下加載文章
原理:加載上一篇或下一篇文章只需要監聽scrollView的滾動,判斷加載上一篇還是下一篇,那麼,我們就要在拖拽結束的時候進行監聽。而動畫效果,需要兩個輔助的動畫View實現,一個是在頂部的TopAnimatedView,一個是在底部的BottomAnimatedView。布局如下圖:

上下加載文章結構分析@2x.png
拿加載上一篇的效果進行說明,其動畫效果是,topAnimatedView向下移動,動畫結束後還原,再重新加載webView即可。
因此,轉化為對應的代碼就是
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if scrollView.contentOffset.y <= -75 && index != 0{ webView.startLoading() UIView.animate(withDuration: 0.3, animations: { self.topAnimatedView.transform = CGAffineTransform.init(translationX: 0, y: (screenH + 20)) }, completion: { (state) in if state { self.topAnimatedView.transform = CGAffineTransform.identity // 加載上一篇文章 self.didSetIndex(self.index - 1) self.loadData() } }) } }
總結
以上就是整個話題詳情的要點了,有不明白的可以留言~ 源碼地址:https://github.com/maple1994/RxZhiHuDaily