【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