前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【iOS】仿知乎日报,RxSwift-Part2-详情页的搭建

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

作者头像
MapleYe
发布2020-03-30 17:04:28
8270
发布2020-03-30 17:04:28
举报
文章被收录于专栏:MapleYeMapleYe

前言

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

分析

还是先来看下演示gif

详情页.gif

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

{
  "body": "<div class=\"main-wrap content-wrap\">\n<div class=\"headline\">\n\n<div class=\"img-place-holder\"></div>\n\n\n\n</div>\n\n<div class=\"content-inner\">\n\n\n\n\n<div class=\"question\">\n<h2 class=\"question-title\">机会成本是否有「时效性」?</h2>\n\n<div class=\"answer\">\n\n<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>\n\n<div class=\"content\">\n<p>是的,机会成本是一个非常简化的概念,题主敏锐的发现了这个问题。机会成本特别适合<strong>静态、有限选择、风险因素不重要</strong>时候的分析,但是当存在风险、选择无限、动态问题的时候,机会成本这一概念就显得过于简单了。</p>\r\n<p>机会成本遗漏了<strong>风险结构</strong>,两块钱可以买一瓶水,也可以买彩票;可以买奖金 500 万但是中奖率千万分之一的大彩票,也可以买奖金 10 块但是中间率高很多的小彩票。买大彩票还是小彩票不光取决于机会成本(以期望收益计算),也取决于个人的风险偏好。技术性地讲,机会成本特别适用一阶随机占优时候的比较,但是当风险是主要因素的时候就不太适用。</p>\r\n<p>而且两块钱买一瓶水 vs 两块钱买张彩票,和 200 块钱买 100 瓶水 vs 100 张彩票又不一样。我可以花其中的 180 块钱去买水,剩下的钱买彩票,这样的选择有非常多种。这样的选择有非常多。我们当然依然可以列出所有的选项,然后从中挑选一个最偏好的方案。但是更方便的办法可能是用<strong>边际效用</strong>来描述这个新的选择问题。</p>\r\n<p>题主所说的时效性,我举另一个例子。比如题主在考前纠结是看电影还是复习。看电影要花 30 块钱买票,还要搭上两小时的时间,这时候的机会成本就是 30 块钱 + 两小时的复习量(同时也可以思考复习的机会成本是啥)。但是如果看了一半发现电影很无聊,考虑要不要回去复习,那么这时候的机会成本就是一小时的复习量。而回去复习的机会成本就是剩下一小时的愉悦 + 可能的彩蛋。(看,又有“可能性”的问题)。可以看到机会成本是随着时间不断变化的。如果题主在看电影的每时每刻都在做这样的比较,那么用机会成本来刻画选择就会变得非常复杂,一个更好的选择是做成动态规划问题。</p>\r\n<p>曼昆一开始就介绍机会成本的概念是因为它非常简单、符合直觉,并且生活中非常多的问题确实也是可以用机会成本的概念思考的。我上面说的有些名词不理解并无所谓,后来慢慢都会知道的。题主刚接触经济学就能有这样反思概念的意识非常好,经济学就是这样不断在概念和反思概念中发展起来的。</p>\n</div>\n</div>\n\n\n<div class=\"view-more\"><a href=\"http://www.zhihu.com/question/66457929\">查看知乎讨论<span class=\"js-question-holder\"></span></a></div>\n\n</div>\n\n\n</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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 分析
  • 要点解析
    • 1、自定义WKWebView
      • 2、拼接HTML
        • 3、内容自适应
          • 4、上下加载文章
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档