OC

iOS开发阅读器实现原理

Posted by sunzhongliang on June 11, 2019

数据模型认知以及分页处理逻辑

小说一般具有目录、章节、作者、页码、发布日期等等;但归结于APP来说,处理难点在于数据模型设计以及分页的设计,好的分页设计在于当用户加载完数据之后无卡顿现象
若以页码为基准数据在APP端进行分页,则存在不同屏幕尺寸下可能显示不完整的现象
因此可采用以章节为基准数据由客户端进行分页处理,不同设备屏幕下可能页码数不一致(这也合乎常理)

每一章节字数可能在1万字左右,按照utf-8编码的情况下,一个中文大概占用3个byte,一万字大概只有30KB左右
若采用在线浏览文章的话,数据请求把每一章节下载下来,请求内容也不会太大

分页方式

当数据请求把章节拉下来之后,APP端可在后台开启异步线程,对当前章节进行分页,分页方式可使用UITextKit框架下的排版对象。分页完成后采用UITextView来渲染

TextKit

要想让一个文本引擎工作,需要以下几个参与者:
字符串
要绘制的文本,由NSTextStorage保存并管理这个字符串
NSTextStorage
管理所有的属性和文本信息,是NSMutableAttributedString的子类,系统只提供了两个存取器方法存取它们,并另外提供了两个方法来分别修改文本和属性。 NSLayoutManager
是一个中心组件,负责对文字进行编辑排版处理

  1. 监听 TextStorage 中文本或属性改变的通知,一旦接收到通知就触发布局进程
  2. 从 TextStorage 提供的文本开始,它将所有的字符翻译为字形(Glyph)
  3. 一旦字形全部生成,这个管理器向它的 TextContainers 查询文本可用以绘制的区域
  4. 然后这些区域被行逐步填充,而行又被字形逐步填充。一旦一行填充完毕,下一行开始填充
  5. 对于每一行,布局管理器必须考虑断行行为(放不下的单词必须移到下一行)、连字符、内联的图像附件等等
  6. 当布局完成,文本的当前显示状态被设为无效,然后 LayoutManager 将前面几步排版好的文本设给 Text View

NSTextContainer
文本可以排版的区域。

示例

可以抽出一个分页方法,将章节数据传给此方法,返回每一页的起始位置,然后控件根据每页的起始位置,从数据源截取数据并展示

-(NSArray *)pagingWithContentString:(NSString *)contentString
                        contentSize:(CGSize)contentSize
                      textAttribute:(NSDictionary *)textAttribute
{
    
    NSMutableArray *pageArray = [NSMutableArray array];
    NSMutableAttributedString *orginAttributeString = [[NSMutableAttributedString alloc] initWithString:contentString attributes:textAttribute];
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:orginAttributeString];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc]init];
    [textStorage addLayoutManager:layoutManager];
    
    while (YES) {
        NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:contentSize];
        [layoutManager addTextContainer:textContainer];
        NSRange rang = [layoutManager glyphRangeForTextContainer:textContainer];
        if(rang.length <= 0)
        {
            break;
        }
        
        [pageArray addObject:[NSValue valueWithRange:rang]];
    }
    return pageArray;
}

这个方法返回了一个NSRange对象,NSRange对象保存了章节中每一页字数的起始位置。由于小说内容是不可被修改的,因此可以将NSRange对象和章节标识关联起来缓存到本地,下次进入APP就不再需要繁重的排版任务计算逻辑了

本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.