前言
在上一篇,我们搭建了首页。而这篇,我们将开始搭建话题详情页。
分析
还是先来看下演示gif
再结合话题详情的接口分析 http://news-at.zhihu.com/api/4/news/9649565。具体的json格式如下:
{
"body": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n机会成本是否有「时效性」?
\n\n\n\n\n是的,机会成本是一个非常简化的概念,题主敏锐的发现了这个问题。机会成本特别适合静态、有限选择、风险因素不重要时候的分析,但是当存在风险、选择无限、动态问题的时候,机会成本这一概念就显得过于简单了。
\r\n机会成本遗漏了风险结构,两块钱可以买一瓶水,也可以买彩票;可以买奖金 500 万但是中奖率千万分之一的大彩票,也可以买奖金 10 块但是中间率高很多的小彩票。买大彩票还是小彩票不光取决于机会成本(以期望收益计算),也取决于个人的风险偏好。技术性地讲,机会成本特别适用一阶随机占优时候的比较,但是当风险是主要因素的时候就不太适用。
\r\n而且两块钱买一瓶水 vs 两块钱买张彩票,和 200 块钱买 100 瓶水 vs 100 张彩票又不一样。我可以花其中的 180 块钱去买水,剩下的钱买彩票,这样的选择有非常多种。这样的选择有非常多。我们当然依然可以列出所有的选项,然后从中挑选一个最偏好的方案。但是更方便的办法可能是用边际效用来描述这个新的选择问题。
\r\n题主所说的时效性,我举另一个例子。比如题主在考前纠结是看电影还是复习。看电影要花 30 块钱买票,还要搭上两小时的时间,这时候的机会成本就是 30 块钱 + 两小时的复习量(同时也可以思考复习的机会成本是啥)。但是如果看了一半发现电影很无聊,考虑要不要回去复习,那么这时候的机会成本就是一小时的复习量。而回去复习的机会成本就是剩下一小时的愉悦 + 可能的彩蛋。(看,又有“可能性”的问题)。可以看到机会成本是随着时间不断变化的。如果题主在看电影的每时每刻都在做这样的比较,那么用机会成本来刻画选择就会变得非常复杂,一个更好的选择是做成动态规划问题。
\r\n曼昆一开始就介绍机会成本的概念是因为它非常简单、符合直觉,并且生活中非常多的问题确实也是可以用机会成本的概念思考的。我上面说的有些名词不理解并无所谓,后来慢慢都会知道的。题主刚接触经济学就能有这样反思概念的意识非常好,经济学就是这样不断在概念和反思概念中发展起来的。
\n\n\n\n\n \n\n\n\n\n\n\n\n",
"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网页
fileprivate func loadHTML(model: MPStoryDetailModel) {
guard let css = model.css, let body = model.body else {
return
}
var html = ""
html += ""
css.forEach { html += "" }
html += ""
html += ""
html += body
html += ""
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。布局如下图:
拿加载上一篇的效果进行说明,其动画效果是,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