在 iOS 中如果想加载显示 HTML 文本,一般有以下的几种方案:
- 使用 WKWebView ,偏重、性能较差
- 将 HTML 字符串转换为
NSAttributedString
对象,使用 UITextView,UILabel… - 使用一些三方库,如 DTCoreText、SwiftSoup
- 自己去解析标签实现,较复杂。
对于一些详情原生页面,加载一段功能简单的 html 标签文本,使用 NSAttributedString + UITextView
是一种相对轻量的选择,本文也只讨论这种方式。
然而在实际开发的过程中,我们很容易发现一些问题:
1、html 字符串转 NSAttributedString 是同步的,文本稍大一点,就会阻塞主线程,页面卡死。
这个问题好解决,直接将转换操作放到子线程去做就好
1 | objc复制代码dispatch_async(dispatch_get_global_queue(0, 0), ^{ |
2、如果只是一些片段 html 标签,转换后的样式可能不太美观,可以加一些 CSS 来美化。
比如字体默认太小,图片显示太宽等。这时我们可以自己拼接一些 CSS 进去,下面代码我们增加默认字体大小 16px,图片宽度为 textView 宽度,高度自适应。
1 | xml复制代码CGFloat contentWidth = self.textView.bounds.size.width; |
你也可以去遍历 NSParagraphStyleAttributeName
属性,来设置一些 style。
1 | objc复制代码// 设置行高 |
3、加载图片过大、多图时,首次显示很慢,拼网络了。
这种时候,我们可以先使用正则找出所有的 <img>
,然后有两个种方案选择:
1、将所有 <img>
删除, 先显示无图片的文本内容,再去加载原始带图片的 html。
1 | objc复制代码NSString *pattern = @"<img[^>]*>"; |
2、将所有 <img>
替换为本地的默认图片,先显示带默认图片的,再去加载原始的 html。
1 | objc复制代码 // 使用占位图 |
这样,我们能减少首次显示的时间。
4、实现图片点击,能查看大图
在设置 UITextViewDelegate 代理后,通过代理方法去拦截。
1 | objc复制代码self.textView.delegate = self; |
虽然能拦截到图片点击了,但是拿不到图片的 url,以及点击的是第几张图片。不过,如果加载的图片来自 fileURL,那就能拿到文件名 NSString *fileName = textAttachment.fileWrapper.filename;
我们完全可以实现一套 HTML 里的图片缓存,使用正则匹配出所有 <img src=>
获取到图片 url , 借助 SDWebImage 去下载图片保存到本地,再用这个图片 fileURL 去替换掉原 src的内容。即达到了使用自己缓存的目的,这样加载出来的图片,点击时,可以知道点击的图片名 filename
。
1 | objc复制代码// 仅部分代码,完整的请看 demo: https://github.com/iHongRen/UITextView-html-demo |
拿到 filename 之后,我们就可以匹配出点击的图片 url 以及 索引位置
1 | objc复制代码NSString *fileName = textAttachment.fileWrapper.filename; |
5、上面的方法还需要注意,如果图片的 src 是 base64 url,需要特殊处理
将 base64 字符串直接转为图片,再存储到本地
1 | objc复制代码// NSString+Html.m 类别 |
6、还有一种较为简单的方法能获取点击的图片
通过遍历所有 NSAttachmentAttributeName 与点击的 textAttachment 对比,从而找到对应点击的图片索引
1 | objc复制代码- (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction { |
7、如果你的 html 比较复杂,在一些机型上可能会遇到加载卡死、崩溃。
你可以关闭一些属性,这些属性会增加布局复杂性和计算成本,导致渲染卡死
1 | objc复制代码// NSLayoutManager 不会考虑字体领先间距,行高将仅包括字体的实际高度,而不会有额外的垂直间距 |
8、如果使用 textView 的宽度去计算富文本高度,再把这个高度赋予 textView 时,内容显示不完整。
这是因为 textView 有默认自带的边距,导致计算用的宽度和显示的宽度不一致。
1 | objc复制代码// 取消默认的边距 |
9、如果你想保留 textView 可交互,又要禁止它的长按弹出菜单(拷贝,选择,…)
1 | objc复制代码self.textView.editable = YES; |
10、默认链接是用外部浏览器打开的,如果你想用 App 内 webView 打开,可以拦截 url 的交互
1 | objc复制代码- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction { |
如果本文对您有帮助,请给个小赞👍🏻。
demo 地址:github.com/iHongRen/UI…
demo 中有耗时显示,但不同机型测试结果不一。
本文转载自: 掘金