iOS Development Guidelines
  • Introduction
  • 规范
    • 0. 介绍
    • 1. 序言
    • 2. 代码命名规范
      • 2.1. 代码命名基础
      • 2.2. 方法(Method)命名
      • 2.3. 函数(Function)命名
      • 2.4. 属性(Property)与数据类型命名
      • 2.5. 其它命名规范
      • 2.6. 可接受缩略名
    • 3. 代码格式规范
      • 3.1. 代码注释格式
      • 3.2. 代码结构与排版
    • 4. 开发实践
      • 4.1. Objective-C保留字
    • 5. Xcode工程结构
    • 6. 版本控制
      • 6.1. Git基本配置
      • 6.2. Git分支模型
      • 6.3. SVN源代码管理规范
      • 6.4. SVN的标准目录结构
    • 7. 附录
      • 7.1. Xcode扩展插件
      • 7.2. 第三方开源库
    • 8. 参考
    • 9. iOS开发优化
  • Swift编码规范
  • Objective-C新特性
  • iOS生命周期
  • Apple 官方设计指南
    • iOS 人机交互指南
      • 概览 - 设计理念
      • 概览 - iOS 10 新功能
      • 概览 - 接口要素
      • 交互 - 3D Touch
      • 交互 - 辅助功能
      • 交互 - 音频
      • 交互 - 身份验证
      • 交互 - 数据输入
      • 交互 - 反馈
      • 交互 - 文件处理
      • 交互 - 初次启动体验
      • 交互 - 手势
      • 交互 - 加载
      • 交互 - 模态
      • 交互 - 导航
      • 交互 - 评分和评论
      • 交互 - 请求权限
      • 交互 - 设置
      • 交互 - 术语
      • 交互 - 撤销与重做
      • 系统功能 - 多任务
      • 系统功能 - 通知
      • 系统功能 - 打印
      • 系统功能 - 快速预览
      • 系统功能 - Siri
      • 系统功能 - TV 供应商
      • 可视化设计 - 动画
      • 可视化设计 - 品牌化
      • 可视化设计 - 颜色
      • 可视化设计 - 布局
      • 图像 - 应用图标
  • Apple 官方开发指南
    • App 发布指南
      • 待完善
    • Cocoa 代码指南
      • 代码命名基础
      • 方法命名
      • 函数命名
      • 属性和数据类型命名
      • 可接受的缩写词和首字母缩写词
      • 针对框架开发者的技术细节
    • 核心蓝牙编程指南
      • 待完善
  • iOS 杂谈
    • Auto Layout 是怎么进行自动布局的性能如何
    • App 启动速度的优化与监控
    • 多人的大项目,架构怎么设计更合理
    • 链接器:符号是怎么绑定到地址上的
    • App 如何通过注入动态库的方式实现极速编译调试
    • 静态分析工具的选择
    • Clang的App 提质
    • 无侵入的埋点方案如何实现
    • 包大小:如何从资源和代码层面实现全方位瘦身
    • iOS 崩溃千奇百怪如何全面监控
    • 如何利用 RunLoop 原理去监控卡顿
    • 临近 OOM,如何获取详细内存分配信息,分析内存问题
    • 日志监控:怎样获取 App 中的全量日志
    • 性能监控:衡量 App 质量的那把尺
    • 远超想象的多线程的那些坑
    • 怎么减少 App 电量消耗
    • 除了 Cocoa,iOS还可以用哪些 GUI 框架开发
    • 细说 iOS 响应式框架变迁,哪些思想可以为我所用
    • 如何构造酷炫的物理效果和过场动画效果
    • A/B 测试:验证决策效果的利器
    • 怎样构建底层的发布和订阅事件总线
    • 如何提高 JSON 解析的性能
    • 如何用 Flexbox 思路开发?跟自动布局比,Flexbox 好在哪
    • 怎么应对各种富文本表现需求
    • 如何在 iOS 中进行面向测试驱动开发和面向行为驱动开发
    • 如何制定一套适合自己团队的 iOS 编码规范
    • iOS 系统内核 XNU:App 如何加载
    • iOS 黑魔法 Runtime Method Swizzling 背后的原理
    • libffi:动态调用和定义 C 函数
    • iOS 是怎么管理内存的
    • 如何编写 Clang 插件
    • 打通前端与原生的桥梁:JavaScriptCore 能干哪些事情
    • React Native、Flutter 等,这些跨端方案怎么选
    • 原生布局转到前端布局,开发思路有哪些转变
    • iOS原生、大前端和Flutter分别是怎么渲染的
    • 剖析使 App 具有动态化和热更新能力的方案
  • Flutter
    • 0.Flutter学习笔记以及问题记录
    • 1.Dart基础快速入门
    • 2.什么是声明式UI
    • 3.Flutter入门基础知识
    • 4.项目结构、资源、依赖和本地化
    • 6.布局与列表
    • 7.状态管理
    • 8.路由与导航
    • 9.手势检测及触摸事件处理
    • 9.线程和异步UI
    • 10.主题和文字处理
    • 11.表单输入与富文本
    • 12.调用硬件、第三方服务以及平台交互、通知
    • 13.基于Http实现网络操作
    • 14.图片控件开发详解
    • 15.异步:Future与FutureBuilder实用技巧
    • 16.APP首页框架搭建-Scaffold与PageView
Powered by GitBook
On this page
  • WebView
  • YYText
  • 小结

Was this helpful?

  1. iOS 杂谈

怎么应对各种富文本表现需求

怎么应对各种富文本表现需求

在iOS开发中,富文本的展示是一个非常常见的需求。为了帮助你更好地了解如何展示富文本,我在今天这篇文章中,会结合一个项目来跟你说说面对富文本展示需求时,要怎么考虑和实现。这样,你在自己的项目中,也可以借鉴今天这样的实现思路和方法。

简单来说,富文本就是一段有属性的字符串,可以包含不同字体、不同字号、不同背景、不同颜色、不同字间距的文字,还可以设置段落、图文混排等等属性。

我以前做过一个 RSS 阅读器,阅读器启动后,需要抓取最新的 RSS 内容进行展示。RSS 里面的文章内容属于富文本,是用HTML标签来描述的,包含了文字样式、链接和图片。

比如,RSS阅读器中的某篇文章内容如下:

<item><title>涉国资流失嫌疑 东方广益6亿元入股锤子科技被调查</title><link>https://www.cnbeta.com/articles/tech/841851.htm</link><description><![CDATA[<p><strong>据虎嗅得到的独家消息,成都成华区监察委已立案调查“东方广益6亿元入股锤子科技(北京)股份有限公司”事宜,认为这个项目有国有资产流失嫌疑。</strong>成华区监察委员会成立于2018年2月,按照管理权限对全区行使公权力的公职人员依法实行监察,履行监督、调查和处置职责。</p> <a href="https://www.cnbeta.com/articles/tech/841851.htm" target="_blank"><strong>阅读全文</strong></a>]]></description><author>ugmbbc</author><source>cnBeta.COM</source><pubDate>Sat, 27 Apr 2019 09:46:45 GMT</pubDate><guid>https://www.cnbeta.com/articles/tech/841851.htm</guid></item>

文章的 HTML 代码就在上面 RSS 中的 description 标签里。解析出 RSS 中所有文章的 HTML 代码,并将它们保存到本地数据库中。

接下来,如何展示 HTML 内容呢?当时,我的第一反应就是使用 WebView 控件来展示。

WebView

使用 WebView 显示文章只需要创建一个 UIWebView 对象,进行一些基本滚动相关的设置,然后读取 HTML 字符串就可以了,具体实现代码如下:

self.wbView = [[UIWebView alloc] init];self.wbView.delegate = self;[self.view addSubview:self.wbView];[self.wbView mas_makeConstraints:^(MASConstraintMaker *make) {    make.top.left.right.bottom.equalTo(self.view);}];self.wbView.scalesPageToFit = YES; // 确保网页的显示尺寸和屏幕大小相同self.wbView.scrollView.directionalLockEnabled = YES; // 只在一个方向滚动self.wbView.scrollView.showsHorizontalScrollIndicator = NO; // 不显示左右滑动[self.wbView setOpaque:NO]; // 默认是透明的// 读取文章 html 字符串进行展示[self.wbView loadHTMLString:articleString baseURL:nil];

和 UIWebView 的 loadRequest 相比,UIWebView 通过 loadHTMLString 直接读取 HTML 代码,省去了网络请求的时间,展示的速度非常快。不过,HTML 里的图片资源还是需要通过网络请求来获取。所以,如果能够在文章展示之前就缓存下图片,那么无需等待,就能够快速完整地展示丰富的文章内容了。

那么,我应该使用什么方案来缓存文章中的图片呢?

在 Cocoa 层使用 NSURLProtocol 可以拦截所有 HTTP 的请求,因此我可以利用 NSURLProtocol 来缓存文章中的图片。

接下来,我再来和你说说,如何用我写的一个 Web 页面预加载库 SSMURLCache来预缓存 HTML 里的图片。这个库你也可以应用到自己项目中。

首先,我需要从数据库中取出所有未缓存图片的文章内容 HTML。实现代码如下:

[[[[[SMDB shareInstance] selectAllUnCachedFeedItems] subscribeOn:[RACScheduler schedulerWithPriority:RACSchedulerPriorityDefault]] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSMutableArray *x) {    // 在数据库中获取所有未缓存的文章数据 x    NSMutableArray *urls = [NSMutableArray array];    if (x.count > 0) {        self.needCacheCount = x.count;        for (SMFeedItemModel *aModel in x) {            // 将文章数据中的正文内容都存在 urls 数组中            [urls addObject:aModel.des];        }    }    ...    }];

如上面代码所示,在数据库中获取到所有未缓存文章的数据后,遍历所有数据,提取文章数据中的正文 HTML 内容保存到一个新的数组 urls 中。

然后,使用 SSMURLCache 开始依次预下载文章中的图片进行缓存。实现代码如下:

[[SSMURLCache create:^(SSMURLCacheMk *mk) {    mk.whiteUserAgent(@"gcdfetchfeed").diskCapacity(1000 * 1024 * 1024);}] preloadByWebViewWithHtmls:[NSArray arrayWithArray:urls]].delegate = self;

SSMURLCache 使用 preloadByWebViewWithHtmls 方法去预缓存所有图片,在 SSMURLCache 初始化时,会设置 UserAgent 白名单,目的是避免额外缓存了其他不相关 UIWebView 的图片。

缓存图片的核心技术还是 NSURLProtocol,SSMURLCache 最终也是使用 NSURLProtocol 来缓存图片的。NSURLProtocol 是一个抽象类,专门用来处理特定协议的 URL 数据加载。你可以使用自定义 URL 处理的方式,来重新定义系统 URL 加载。SSMURLCache 缓存图片的具体实现代码,可以在STMURLProtocol这个类里查看。

STMURLProtocol 会在所有网络请求的入口 canInitWithRequest 方法中加上过滤条件,比如 SSMURLCache 在初始化时设置 UserAgent 白名单,过滤代码如下:

// User-Agent来过滤if (sModel.whiteUserAgent.length > 0) {    // 在 HTTP header 里取出 User Agent    NSString *uAgent = [request.allHTTPHeaderFields objectForKey:@"User-Agent"];    if (uAgent) {        // 不在白名单中返回 NO,不会进行缓存        if (![uAgent hasSuffix:sModel.whiteUserAgent]) {            return NO;        }    } else {        return NO;    }}

UserAgent 白名单过滤会通过 request 的 allHTTPHeaderFields 获取到当前网络请求的 UserAgent,然后和已经设置的 UserAgent 白名单做比较:如果在白名单中就进行缓存;否则,就不会缓存。

STMURLProtocol 还可以根据域名进行过滤,这样可以灵活、精确地控制缓存范围。如果你设置了域名白名单,那么只有在白名单里的域名下的网络请求才会执行缓存,过滤代码如下:

//对于域名白名单的过滤if (sModel.whiteListsHost.count > 0) {    id isExist = [sModel.whiteListsHost objectForKey:request.URL.host];    // 如果当前请求的域名不在白名单中也会返回 NO    if (!isExist) {        return NO;    }}

如代码所示,当前网络请求的域名可以通过 request.URL.host 属性获取到,获取到网络请求的域名后,再去看域名白名单里是否有,如果有就缓存,没有就返回 NO,不进行缓存操作。

在 canInitWithRequest 方法中满足缓存条件后,开始缓存的方法是 startLoading。startLoading 方法会判断已缓存和未缓存的情况,如果没有缓存会发起网络请求,将请求到的数据保存在本地。如果有缓存,则会直接从本地读取缓存,实现代码如下:

// 从缓存里读取数据NSData *data = [NSData dataWithContentsOfFile:self.filePath];NSURLResponse *response = [[NSURLResponse alloc] initWithURL:self.request.URL MIMEType:[otherInfo objectForKey:@"MIMEType"] expectedContentLength:data.length textEncodingName:[otherInfo objectForKey:@"textEncodingName"]];[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];// 使用 NSURLProtocolClient 的 URLProtocol:didLoadData 方法加载本地数据[self.client URLProtocol:self didLoadData:data];[self.client URLProtocolDidFinishLoading:self];

如代码所示,STMURLProtocol 先通过缓存的路径获取到缓存的数据,再使用 NSURLProtocolClient 的 URLProtocol:didLoadData 方法加载本地缓存数据,以减少网络请求。

显示文章内容时使用 NSURLProtocol,对于那些已经缓存过图片的文章就不用发起图片的网络请求,显示的速度跟本地加载显示速度一样快。

虽然通过 URLProtocol 重新定义系统 URL 加载的方式,来直接读取预缓存提升了加载速度,但在长列表的 Cell 上展示富文本,就需要性能更高、内存占用更小的方法。那么接下来,我们再看看除了 UIWebView 还有没有什么方法可以展示富文本呢?

当然还有了。

其中,YYText 不仅兼容 UILabel 和 UITextView,在异步文字布局和渲染上的性能也非常好。所以接下来,我们就一起看看 YYText是如何展示富文本的吧。

YYText

集成 YYText 到你的App非常简单,只需要在 Podfile 中添加 pod ‘YYText’ 就可以了。下面代码展示了如何展示图文混排的富文本:

NSMutableAttributedString *text = [NSMutableAttributedString new];UIFont *font = [UIFont systemFontOfSize:16];NSMutableAttributedString *attachment = nil;  // 嵌入 UIImageUIImage *image = [UIImage imageNamed:@"dribbble64_imageio"];attachment = [NSMutableAttributedString yy_attachmentStringWithContent:image contentMode:UIViewContentModeCenter attachmentSize:image.size alignToFont:font alignment:YYTextVerticalAlignmentCenter];[text appendAttributedString: attachment];  // 嵌入 UIViewUISwitch *switcher = [UISwitch new];[switcher sizeToFit];attachment = [NSMutableAttributedString yy_attachmentStringWithContent:switcher contentMode:UIViewContentModeBottom attachmentSize:switcher.size alignToFont:font alignment:YYTextVerticalAlignmentCenter];[text appendAttributedString: attachment];  // 嵌入 CALayerCASharpLayer *layer = [CASharpLayer layer];layer.path = ...attachment = [NSMutableAttributedString yy_attachmentStringWithContent:layer contentMode:UIViewContentModeBottom attachmentSize:switcher.size alignToFont:font alignment:YYTextVerticalAlignmentCenter];[text appendAttributedString: attachment];

如代码所示,YYText 对于富文本的图文混排使用的是自定义的 NSMutableAttributedString 分类,自定义分类不光简化了 NSMutableAttributedString,还增加了功能,除了图片外,可以嵌入 UIView 和 CALayer。

通过上面 YYText 描述富文本的代码,你会发现原生代码描述富文本跟 HTML 比,既复杂又啰嗦。HTML 代码更易读、更容易维护,所以除了长列表外,我建议你都使用 HTML 来描述富文本。

对于 UIWebView 内存占用高的问题,你可以考虑使用 HTML 代码转原生代码的思路解决。

小结

今天我跟你介绍了如何通过 UIWebView 和 YYText 来展示富文本。

UIWebView 展示的是使用 HTML 描述的富文本。HTML 是描述富文本最简单和最常用的方式,相对于 YYText 或 TextKit 那样描述富文本的方式来说,更加简洁和标准。不过,UIWebView 的缺点也比较明显,同时创建多个 UIWebView 实例,对于内存的占用会非常大。

所以,我对于富文本展示的建议是,如果是列表展示富文本建议使用 TextKit 或者 YYText,其他情况可以选择使用 UIWebView 来展示富文本。

Previous如何用 Flexbox 思路开发?跟自动布局比,Flexbox 好在哪Next如何在 iOS 中进行面向测试驱动开发和面向行为驱动开发

Last updated 3 years ago

Was this helpful?

在长列表这种场景下,如果不用 HTML 来描述富文本的话,想要使用原生 iOS 代码来描述富文本的话,你还可以使用苹果官方的和 来展示。

TextKit
YYText