废话不多说,先上项目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,两种方式渲染出来的效果居然不一样。(这种情况只出现在中文,如果是全英文就没有问题)
再经过一番研究后发现这是字体造成的,中文如果用
[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;
}
这样我们就可以得到每一章的字符串了,然后再开始的时候只加载一章的内容,在翻到这一章最后一页的时候,再翻页则加载下一章的内容。
终于最核心的分页做完了。这样就可以正常的阅读了。