txt文本分页

毕业设做的是电子书,当时想做一个类似anyview带论坛社交功能的阅读器,最后做了个四不像出来,当然,做毕业设计还是绰绰有余的,现在回头看看,心真大。功能点太多太杂,如果几个人做还是有点搞头的,一个做想想都觉得这个坑太巨了。把代码翻出来看看,有重做的欲望,这次先做阅读和同步两个核心功能的大体,其它细节慢慢在实现。
先说说分页

textview分页

txt文本分页在网上搜到的方法大概就两种,其中一种就是把文本装载到textview前,按以下方法分页。

  • 首先装载文本内容
  • 让NSAttributedString按字体大小,在指定的显示宽度下进行排序排版,还可以调节行间距其它什么的,这样就能获得显示总高度
  • 由屏幕高度(或者指定高度)和排版的总高度可以得出一个供参考用的总页数,进一步得出一个供参考用的每页显示的文本长度
  • 然后以这个参考文本长度截取原来的文本内容,进行排版,增加或减少文本长度以适应屏幕高度,记录下range,再截取下一段文本,如此循环
  • 最后便可以由range数组来加载各页数据。
  • 代码如下:*
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
+ (instancetype)pageviewWithBookDirectory:(NSString *)bookDirectory andRangesArray:(NSArray *)rangsArray
{
//初始化 pageview
SEPHI_PageView *pageview = [[self alloc] init];
//装入文本内容
pageview.text = [[NSString alloc] initWithContentsOfFile:bookDirectory encoding:NSUTF8StringEncoding error:nil];
if (!pageview.text) {
pageview.text = [[NSString alloc] initWithContentsOfFile:bookDirectory encoding:0x80000632 error:nil];
}
//按字体大小排版
NSAttributedString *textString = [[NSAttributedString alloc] initWithString:pageview.text attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:FONT_SIZE_MAX] }];
if (!rangsArray) {
NSLog(@"进入if");
int referTotalPages;
int referCharatersPerPage;
pageview.totalPages = 0;


//设置不限制行数
// pageview.textView.numberOfLines = 0;

//计算出整个文本的尺寸

CGRect totalTextSize = [textString boundingRectWithSize:CGSizeMake(kScreenWidth - 10, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin context:nil];

// 如果一页就能显示完,直接显示所有文本串即可。

// if (totalTextSize.size.height < kScreenHeight)
// {
// 计算理想状态下的页面数量和每页所显示的字符数量,只是拿来作为参考值用而已!
//总字符长度
NSInteger textLength = pageview.text.length;
//参考总页数
referTotalPages = ((int)totalTextSize.size.height/(int)kScreenHeight) + 1;
//参考每页字符数
referCharatersPerPage = (int)textLength/referTotalPages;

// 申请最终保存页面NSRange信息的数组缓冲区
int maxPages = referTotalPages;
pageview.rangeOfPages = (NSRange *)malloc(referTotalPages*sizeof(NSRange));
memset(pageview.rangeOfPages, 0x0, referTotalPages*sizeof(NSRange));

// 页面索引
int page = 0;
NSRange range;
range.length = referCharatersPerPage;


for (NSUInteger location = 0; location < textLength; )
{
range.length = referCharatersPerPage;
// 先计算临界点(尺寸刚刚超过UILabel尺寸时的文本串)
range.location = location;

// reach end of text ?
NSString *pageText1;

//最后一页设置
if (range.location + range.length >= textLength)
{
range.length = textLength - range.location;
}

int i = 0;
// 然后一个个缩短字符串的长度,当缩短后的字符串尺寸小于textView的尺寸时即为满足
while (range.length > 0 )
{
i++;
pageText1 = [pageview.text substringWithRange:range];
NSAttributedString *pageText = [[NSAttributedString alloc] initWithString:pageText1 attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:FONT_SIZE_MAX] }];
CGRect pageTextSize = [pageText boundingRectWithSize:CGSizeMake(kScreenWidth - 10, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin context:nil];

if (pageTextSize.size.height <= kScreenHeight) {
range.length = [pageText length];
break;
}
else {
range.length -= 50;
}
}

NSLog(@"第%d页数", page);
// 得到一个页面的显示范围
if (page >= maxPages)
{
maxPages += 10;
pageview.rangeOfPages = (NSRange *)realloc(pageview.rangeOfPages, maxPages*sizeof(NSRange));
}
pageview.rangeOfPages[page++] = range;

// 更新游标
location += range.length;
}
pageview.totalPages = page;
pageview.rangeArray = [pageview rangesToArray:page-1];
NSLog(@"%ld", pageview.rangeArray.count);
// 获取最终页面数量
}else{
// 获取最终页面数量
NSLog(@"进入else");
NSLog(@"%ld", rangsArray.count);
pageview.rangeArray = rangsArray;
pageview.totalPages = rangsArray.count+1;
[pageview arrayToRanges:rangsArray];
}

return pageview;
}

如果文本很大,需要对文本进行分割分页,不然程序就卡住。不过这种方法的分页效率确实不高,主要耗时操作在字符串排版,一次是整个文本排版,还有就是调节过程中要进行0~6次左右的每页排版操作,以章节或者分割字符串长度在5000左右就不会有明显卡顿。

coretext分页

这个分页最早出现在应该是github上一个叫WFReader的项目中,12年的,后面几个star数比较高的电子书都是修改这个项目的,我看的是比较新的那个LSYReader。我说你改就好好改,都三、四百赞了,到处有bug,这bug有些还是原项目没有的,他自己改错的,代码还没什么注释,看得真是辛苦,最大的帮助就是给了我找目录的正则表达式,看这个项目的收获还没另一个只有3、4赞的项目收获多,。

大体思路和上面的分页没差,只不过方式变了,而且不需要一个字一个字地来调整,这里可能比上面的方法省点时间。
还有个槽点就是,github上面txt电子书项目,大都是在初始化的时候对整个文本进行分页,文本稍微大点就卡住了。原因我觉得是因为这些项目大部分都是改出来的,压根没仔细看人家怎么分页的,原项目分页是一个章节为一个txt文本,当然不会卡,后面改的项目不管三七二十一,给10来M的书也是整本放进字符串扔去分页,当然卡死,包括上面那个三四百赞的项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#define TICK   int i = 0;NSDate *startTime = [NSDate date];
#define TOCK NSLog(@"%@==Time%d: %f", NSStringFromClass([self class]), ++i, -[startTime timeIntervalSinceNow]);
-(void)paginateWithBounds:(CGRect)bounds
{

[_pageArray removeAllObjects];
//排版字符串
NSAttributedString *attrString;
CTFramesetterRef frameSetter;
CGPathRef path;
//可变的排版字符串
NSMutableAttributedString *attrStr;
//可变排版字符串以章节内容初始化
attrStr = [[NSMutableAttributedString alloc] initWithString:self.content];
//设置排版细节
NSDictionary *attribute = [LSYReadParser parserAttribute:[LSYReadConfig shareInstance]];
//可变字符串进行排版
NSLog(@"%ld",attrStr.length);
[attrStr setAttributes:attribute range:NSMakeRange(0, attrStr.length)];
TICK
//通过CFAttributedStringRef进行初始化,作为CTFrame对象的生产工厂,负责根据path创建对应的CTFrame
attrString = [attrStr copy];
//耗时操作
frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrString);
TOCK
path = CGPathCreateWithRect(bounds, NULL);


int currentOffset = 0;
int currentInnerOffset = 0;
BOOL hasMorePages = YES;
// 防止死循环,如果在同一个位置获取CTFrame超过2次,则跳出循环
int preventDeadLoopSign = currentOffset;
int samePlaceRepeatCount = 0;

while (hasMorePages) {
if (preventDeadLoopSign == currentOffset) {

++samePlaceRepeatCount;

} else {

samePlaceRepeatCount = 0;
}

if (samePlaceRepeatCount > 1) {
// 退出循环前检查一下最后一页是否已经加上
if (_pageArray.count == 0) {
[_pageArray addObject:@(currentOffset)];
}
else {

NSUInteger lastOffset = [[_pageArray lastObject] integerValue];

if (lastOffset != currentOffset) {
[_pageArray addObject:@(currentOffset)];
}
}
break;
}
//添加每一页的字数
[_pageArray addObject:@(currentOffset)];
//获取参考Frame
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(currentInnerOffset, 0), path, NULL);
//在参考的frame下获取每一页的range
CFRange range = CTFrameGetVisibleStringRange(frame);
//如果这一页字符串长度加字符串位置不等于章节字符串长度,则认为分页仍未完成
if ((range.location + range.length) != attrString.length) {

currentOffset += range.length;
currentInnerOffset += range.length;

} else {
// 已经分完,提示跳出循环

hasMorePages = NO;
}
if (frame) CFRelease(frame);
}
TOCK
CGPathRelease(path);
CFRelease(frameSetter);
_pageCount = _pageArray.count;
}