毕业设做的是电子书,当时想做一个类似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; }
|