iOS 小说阅读器高效分页

废话不多说,先上项目https://github.com/daichuan/DCBooks
项目本身自带一本小说龙王传说。从Mainbundle中复制到沙盒中的。项目只支持读txt类型的文件。其他文件可以用QQ发送到手机然后用其他应用打开,选择DCBooks就可以导入项目了

先说一下设计思路。
当然是用系统自带的UIPageViewController,为什么用这个控件呢?当然是因为翻页动画帮我们做好了。然后就是读取文件的字符串然后分页显示。然后就没有了。是不是很简单哈哈哈。
说的很简单,其实重点和难点就在怎么分页。分页是否准确高效。

重点一读取文件字符串

为什么说读取字符串是个重点呢,因为我在写代码的时候遇到了一个坑爹问题。txt文件的编码格式不确定。不过下载的小说的编码大部分都是utf8和GKB。所以读取文件的时候要多做几次判断,下面是读取文件的方法;

+(NSString *)transcodingWithPath:(NSString *)path
{
    NSURL *fileUrl = [NSURL fileURLWithPath:path];
    NSStringEncoding * usedEncoding = nil;
    //带编码头的如 utf-8等 这里会识别
    NSString *body = [NSString stringWithContentsOfURL:fileUrl usedEncoding:usedEncoding error:nil];
    if(body)
    {
        return body;
    }
    //如果之前不能解码,现在使用GBK解码
    NSLog(@"GBK");
    body = [NSString stringWithContentsOfURL:fileUrl encoding:0x80000632 error:nil];
    if (body)
    {
        return body;
    }

    //再使用GB18030解码
    NSLog(@"GBK18030");
    body = [NSString stringWithContentsOfURL:fileUrl encoding:0x80000631 error:nil];
    if(body)
    {
        return body;
    }else
    {
        return nil;
    }
}

重点二超长字符串分页

先说一下我们展示文字的控件是UITextView,分页方法是用的NSLayoutManager类中的glyphRangeForTextContainer:方法来计算的(没有用过这个类的小伙伴可以搜一下TextKit,主要是NSLayoutManager,NSTextStorage,NSTextContainer这三个类),glyphRangeForTextContainer:方法是传入一个容器NSTextContainer,返回一个字符串的NSRange

-(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];
    int I=0;
    while (YES) {
        I++;
        NSTextContainer *textContainer = [[NSTextContainer alloc]initWithSize:contentSize];
        [layoutManager addTextContainer:textContainer];
        NSRange rang = [layoutManager glyphRangeForTextContainer:textContainer];
        if(rang.length <= 0)
        {
            break;
        }
        NSString *str = [contentString substringWithRange:rang];
        NSMutableAttributedString *attstr = [[NSMutableAttributedString alloc]initWithString:str attributes:textAttribute];
        [pageArray addObject:attstr];
    }
    return pageArray;
}

分页问题解决了,但是在文本渲染的时候却出现了问题。
对于TextView来时,设置富文本有两个方式,一种是通过NSTextStorage设置,一种是直接用attributedText设置,但问题来了同样的NSMutableAttributedString,两种方式渲染出来的效果居然不一样。(这种情况只出现在中文,如果是全英文就没有问题)

image

再经过一番研究后发现这是字体造成的,中文如果用[UIFont systemFontOfSize:20]就会出现这种问题,只要换了字体就行了。其中在系统提供的字体中,能够完美使用的有下面几个,

Heiti SC              黑体-简
Heiti TC              黑体-繁
PingFang TC           平方-简
PingFang HK           平方-繁
PingFang SC           平方-繁

这里参考了https://www.jianshu.com/p/f3251ea3da99

重点三分页效率问题

看到这里是不是觉得分页很简单。天真的你又错了。虽然这样看似完美解决了分页问题。但是glyphRangeForTextContainer这个方法的效率是非常低的,下面是苹果注释文档。

// Returns the range of characters which have been laid into the given container.  This is a less efficient method than the similar -textContainerForGlyphAtIndex:effectiveRange:.
- (NSRange)glyphRangeForTextContainer:(NSTextContainer *)container;

而一本小说往往都会有很多页。如果一下子全部分页,需要很长时间,至少龙王传说全部分页完我等了好几分钟都没有完成,然后就放弃了。我用的熊猫读书也存在类似的问题,点开一本小说的时候回明显卡一下,应该是在加载分页。当然熊猫读书卡的时间很短,最长也就几秒钟。于是我就想到另一种解决方案,不一次性全部分页完,我们只对一个章节进行分页,一个章节大概也就几十页。几乎是瞬间加载完成。要按章节加载就要先把字符串分成一个个章节,下面是章节截取的方法,用的纸正则表达式

#pragma mark - 获取这个字符串text中的所有findText的所在的NSRange
+ (NSMutableArray *)getRangeStr:(NSString *)text findText:(NSString *)findText
{
    NSMutableArray *arrayRanges = [NSMutableArray arrayWithCapacity:3];
    if (findText == nil && [findText isEqualToString:@""])
    {
        return nil;
    }
    NSRange rang = [text rangeOfString:findText options:NSRegularExpressionSearch];
    if (rang.location != NSNotFound && rang.length != 0)
    {
        [arrayRanges addObject:[NSValue valueWithRange:rang]];
        NSRange rang1 = {0,0};
        NSInteger location = 0;
        NSInteger length = 0;
        for (int i = 0;; i++)
        {
            if (0 == i)
            {
                //去掉这个abc字符串
                location = rang.location + rang.length;
                length = text.length - rang.location - rang.length;
                rang1 = NSMakeRange(location, length);
            }
            else
            {
                location = rang1.location + rang1.length;
                length = text.length - rang1.location - rang1.length;
                rang1 = NSMakeRange(location, length);
            }
            //在一个range范围内查找另一个字符串的range
            rang1 = [text rangeOfString:findText options:NSRegularExpressionSearch range:rang1];
            if (rang1.location == NSNotFound && rang1.length == 0)
            {
                break;
            }
            else//添加符合条件的location进数组
                [arrayRanges addObject:[NSValue valueWithRange:rang1]];
        }
        return arrayRanges;
    }
    return nil;
}

+(NSMutableArray *)getChapterArrWithString:(NSString *)text
{
    NSMutableArray *marr = [DCFileTool getRangeStr:text findText:@"\n第.{1,}章.*\r\n"];
    NSMutableArray *strMarr = [NSMutableArray array];
    NSRange lastRange = NSMakeRange(0, 0);
    for (int i = 0; i

这样我们就可以得到每一章的字符串了,然后再开始的时候只加载一章的内容,在翻到这一章最后一页的时候,再翻页则加载下一章的内容。

终于最核心的分页做完了。这样就可以正常的阅读了。

你可能感兴趣的:(iOS 小说阅读器高效分页)