iOS 小说阅读器高效分页

作者: nickNameDC | 来源:发表于2018-03-19 15:07 被阅读2764次

    废话不多说,先上项目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<marr.count; i++) {
            NSValue *value = marr[i];
            NSString *string = [text substringWithRange:NSMakeRange(lastRange.location, value.rangeValue.location - lastRange.location)];
            lastRange = value.rangeValue;
            if([string isEqualToString:@""])
            {
                string = @"\r\n";
            }
            [strMarr addObject:string];
        }
        //最后一章到结尾
        NSString *string = [text substringFromIndex:lastRange.location];
        if([string isEqualToString:@""])
        {
            string = @"\r\n";
        }
        [strMarr addObject:string];
        return strMarr;
    }
    

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

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

    相关文章

      网友评论

        本文标题:iOS 小说阅读器高效分页

        本文链接:https://www.haomeiwen.com/subject/rrwlrttx.html