/ iOS

如何基于WKWebView开发一个功能完善的资讯内容页

前言

对于资讯类的APP来说 良好的阅读体验是必不可少的, 那么如何去开发一个功能完善的资讯文章页面就是本文要说的重点.
相信本文会对很多在做同类功能开发的道友们有很大的帮助 , 如果某只大佬路过也欢迎指点一二.

废话不多说 开讲(chui)~

分析

数据

对于图文混排的富文本形式 , 最好最通用的数据格式当属 HTML , 再加上 CSS 和 JS 的配合, 可以随心所欲的展示出成百上千在不同的样式.
当然 , 除了 HTML 也不排除有使用其他规则的数据格式来表示.
但这里我们还是选择使用 HTML 的数据格式 , 毕竟主流嘛.

展示

我们常见的有几种方式来实现富文本的展示

  • UIWebView
    对于这个控件我们并不陌生, 基于UIKit框架, 使用方式简单, 但是任何东西都有缺点, 无解的内存问题一直让人很头疼, 而且因为接口限制 可以做的东西也不多, 他更适合做一些普通的Web浏览操作.

  • WKWebView
    iOS8后的强大Web控件, 基于WebKit框架, 有丰富的API, 更高的性能, 更小的内存 更小的内存 更小的内存等, 当然缺点也是必不可少, 例如Cookie问题 白屏问题 样式异常问题等等, 不过这些问题随着时间的推移 当然难不倒伟大的猴子们, 其实我个人认为 iOS9之后的WebKit才是真正的WebKit (iOS8的是假的) , 在克服了重重奇葩的BUG后 我还是决定使用WKWebView来作为HTML的展示控件 , 这里不多说 后面再谈.

  • CoreText
    强大的核心文本框架 , iOS中几乎所有文字的东东都是通过它来实现的. 这么强大的框架实现一个图文混排当然不在话下, 可是重点是如何解析HTML的样式来让CoreText知道该怎么做! 这一点的代表我觉得非 DTCoreText 莫属了, 它可以解析HTML中的大部分样式 通过CoreText绘制出来 当然例如一些图片和视频等元素 可以通过UIImageView等原生控件来显示, 灰常666 , 但是! 这种强行的转换对于很多复杂的HTML来说肯定力不从心的, 对于HTML的支持肯定是不如WebView的, 不过简单的还是应对自如 , 这里不多说 , 以后我会抽时间写一个基于它来展示文章的Demo.

功能

那么确定了数据格式和展现方式 , 现在我们来说一说功能.
一个文章页面应该包含哪些功能呢?

  • 字号调节
    因每个人的阅读习惯不同 字号调节这个功能当然也是必不可少 , 动态的改变字体大小.

  • 离线阅读
    这个功能的目的在于用户对于收藏过的或者加载过的文章能够在网络不佳或无网络的情况下继续阅读 , 实现的重点在于文章数据的持久化缓存.

  • 日夜间模式(黑白主题样式)
    我个人觉得日夜间的阅读模式是最基本的两种 , 更好的是可以动态改变字体样式和背景颜色或背景图 , 以满足不同用户的体验 , 当然这也是分产品的 , 读书类的产品当然是要有的 因为长时间的沉浸式阅读对于这方面的体验需求是很细腻的 , 可是对于资讯新闻类的产品却并不是必须的 因为这类产品的用户偏向于舒适快速的阅读 .
    实现重点在于动态改变文字大小和背景的颜色以及优雅自然的过渡.

  • 图片浏览
    当然这个必不可少, 用户在对感兴趣的图片可以进行放大等方式查看 , 并且可以保存到相册等等.

  • 图片状态
    这个不得不说一下 , 每个图片的显示从无到有都是分为不同的状态 例如: 初始加载 - 加载中 - 加载完成 - 加载失败 - 点击加载等等 , 对于每个状态的处理我相信很多人都会忽略掉 , 就拿加载失败来说 , 我见过太多APP的文章图片加载失败了显示出来一个裂图或是问号图 , 真的恶心 , 如果就想看这个图 只能退出这个页面 从新进来看能不能出来 , 这样的体验是灰常糟糕的 , 在开发时你可能不会在意 毕竟一个图片加载失败的概率并不高 可能从开发开始到结束发版都没遇到过一次 , 但是我想说 细节决定成败, 有良好的提示 对于那些网络不佳的用户们来说无疑是最贴心的.

  • 流量优化
    这个很重要 , 用户们灰常在意自己的流量问题 (因为流量 == 钱) , 对于缩略图的处理是必不可少的 , 合理的控制可以节省大量不必要的流量消耗 , 对于省流量模式(仅Wifi网络加载图片)的控制的确是某些用户的刚需 , 这点对于动辄几MB的GIF图尤为重要 , 没有哪个用户喜欢用几十分钟就花掉100MB流量的APP , 如果这样的APP还没被卸载 说明用户可能一直没用过. 实现重点在于如何区分该加载的类型 如何有效的利用已经加载过的图片.

  • 横竖屏适配
    这点并不是必需的 , 还是如果能支持当然是最好了, 实际需不需要还是要看产品本身 , 因为对于某些产品来说 横屏并不是一个好的选择.

开发思路

我们分析完了所包含的功能和要使用的展现形式 , 现在就该整理一下开发的思路了.

首先我们要了解所使用的控件的特性以及要注意的问题 , 根据控件的特性找出最佳的实现方法 , 结合上面我们所提到的功能 我来叙述一下我的开发思路:

文章内容的数据格式是HTML , 使用WKWebView 来加载HTML展现内容.

通常来说 服务端的接口数据并不会把一个完整的HTML字符串给我们 , 而是只有内容部分的HTML字符串 , 基本上是这样的格式:

<p>女朋友和她姐姐是双胞胎,总是分不清!</p>
![](http://img.xxxxxxxxxxxxx.com/crawl/1707_9/19/6eee549a554b1d66409)
<p>猫:喜欢猴子养一个呗,在这折腾我干嘛?</p>
![](http://img.xxxxxxxxxxxxx.com/crawl/1707_3/19/a3b81ed6cff11fa4658.gif)
<p>一看就知道是只痛快狗!</p>
![](http://img.xxxxxxxxxxxxx.com/crawl/1707_3/19/99c786d4b149effb163.gif)
<p>被这个枪电一下不知道会不会死</p>
![](http://img.xxxxxxxxxxxxx.com/crawl/1707_9/19/8bff874e8d875c75083.gif)
<p>今天我们要练习带球撞人</p>
![](http://img.xxxxxxxxxxxxx.com/crawl/1707_9/19/6280e10eeeca7e28181.gif)
<p>居然翻车了,一定不是纯血的阿三</p>
![](http://img.xxxxxxxxxxxxx.com/crawl/1707_9/19/0a594d192a861b94511.gif)
<p>可以吹一辈子了,那么我看你怎么拿下来</p>
![](http://img.xxxxxxxxxxxxx.com/crawl/1707_9/19/da49d9ad2345eb34986.gif)
<p>蓝翔毕业典礼</p>
![](http://img.xxxxxxxxxxxxx.com/crawl/1707_9/19/86e383fac9e24d15935.gif)
<p>请问这锁有何用?</p>
![](http://img.xxxxxxxxxxxxx.com/crawl/1707_9/19/987a7cab7cfe1e79269.gif)
<p>兄弟你有毛病啊~有话直说何必要动手动脚呢~</p>
![](http://img.xxxxxxxxxxxxx.com/crawl/1707_9/19/5c11b1c7185d2141335.gif)
<p>一秒就击倒了……不会是演戏吧?</p>
![](http://img.xxxxxxxxxxxxx.com/crawl/1707_9/19/17eeea5639b2f31b856.gif)
<p><span>开心一刻</span></p>
<p>带女朋友回家见家长:“妈,这是我女朋友。”妈打量了女朋友一番说:“你咋找个这样的?”女朋友瞬间脸色就变了,我赶紧打圆场。“妈,她是我女朋友,说话客气点,我们是真心相爱的。”没想到,妈拉过女朋友说:“闺女你那么漂亮,你咋找个这样的,这让我咋跟你爸妈交待……”</p>

这样做的好处是可以让我们更方便的扩展和调整 , 拿到这些后 我们首先要处理这些字符串 根据需要替换修改 , 最后组装成一个完整的HTML , 然后交给WKWebView显示 , 完整的HTML格式大致如下:

<html>
<head>
<meta http-equiv ="Content-Type" content="text/html;charset=utf-8"/>
<meta name = "viewport" content="width = device-width, initial-scale = 1, user-scalable=no"/>                           
<title></title>
<link href="../css/style.css" rel="stylesheet" type="text/css"/>
<script src="../js/jquery.min.js"></script>
</head>
<body>
<div class="content">
<!-- 内容 -->
</div>                           
</body>                        
</html>

为了实现离线阅读 那么就要做到图片下载的可控, 这里我们可以将页面图片的加载放到原生端来做, 也就是说不通过WebView加载图片, 而是通过原生加载图片的方式来加载页面中的图片, 这样做有几点好处:

  1. 原生加载可控性更高 可以更好掌控下载进度 以及下载完成后对图片的其他处理.
  2. 图片缓存的利用率更好, 图片下载完成以后 Web页面可以使用 原生的图片浏览页也可以使用.
  3. 图片缓存的大小更方便统计以及清理等等.

虽然好处多多 , 但是这样做法并不适用于任何Web页面, 因为别人的HTML写法天知道什么样子, 你很难去处理, 所以说 这种方法仅仅适用于固定样式 固定HTML写法的情况.

优点和缺点讲完了, 我现在简单阐述一下实现Web页面图片原生加载的大致流程.

首先在HTML给WebView加载之前, 要将所有需要使用原生加载的<img>标签过滤出来 , 并且得到每个<img>标签的真实图片地址, 也就是src属性的值, 然后在<img>标签中添加一个自定义属性data-original将真实图片地址设置为它的值, 同时将src的属性值设置为默认的占位图本地图片地址...

<img>标签修改完成后 , 就可以交给WebView去加载了, 这样一来没有图片要加载的页面加载速度会大幅度提高, 在页面加载完成后, 就可以开始下载图片了.

通过JS获取到所有需要下载的<img>标签的data-original属性值 (真实的图片地址), 然后通过原生加载图片的方式进行下载, 下载完成后将图片缓存到本地(沙盒), 最后使用JS将每个图片对应的 <img>标签的src属性设置成本地的图片缓存地址. 此时WebView页面中就会显示本地的缓存图片了.

大致思路就是这样 , 具体实现起来会涉及到很多细节, 例如WKWebView控件的特性问题, 例如缩略图的处理, 图片加载的控制, Gif图片加载的进度等等 , 这里不多啰嗦, Demo已经完成, 内附有比较完善的流程图, 大家可以结合这个思路去阅读代码 运行Demo 自行体会.

往往一个看似简单的功能 内部却蕴含着诸多细节, 而这诸多的细节决定了这个功能的成败. ---------- 尼古拉斯 · LEE 🤣