【iOS】仿知乎日报,RxSwift-Part2-详情页的搭建

2020-03-30 17:04:28 浏览数 (1)

前言

在上一篇,我们搭建了首页。而这篇,我们将开始搭建话题详情页。

分析

还是先来看下演示gif

详情页.gif

再结合话题详情的接口分析 http://news-at.zhihu.com/api/4/news/9649565。具体的json格式如下:

代码语言:javascript复制
{
  "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语句,获取其内容高度:

代码语言:javascript复制
 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>等元素,使之合乎规范。

具体拼接方式如下:

代码语言:javascript复制
/// 加载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语句

代码语言:javascript复制
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即可。

因此,转化为对应的代码就是

代码语言:javascript复制
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

0 人点赞