最近正好有空,花两周多写了个基本的小说阅读器,仿照追书神器,数据源和UI都是抓追书神器,实现了书籍搜索、排行榜、自动缓存、下载等功能;最后会给出github链接,欢迎star,交流,参考学习...
下面整理一下开发中遇到的一些问题:
-
UIPageViewController
UIPageViewController
在UIPageViewControllerTransitionStylePageCurl
模式没有什么问题,在UIPageViewControllerTransitionStyleScroll
模式下,第一次向后滑动时也会响应获取前一页的方法,这个需要注意一下;然后更换章节的时候,调用setViewControllers:direction:animated:completion:
方法,这时候UIPageViewController
没有清空其内部缓存,它认为已经知道前/后一个页面的存在,不会调用dataSource
的代理方法,这时候会导致章节错误;现在的处理方法如下:
__weak typeof(self) wself = self;
[_pageViewController setViewControllers:@[[self readPageViewWithChapter:_chapter page:_page]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished) {
if (finished && !wself.isPageCurlStyle) {
dispatch_async(dispatch_get_main_queue(), ^{
[wself.pageViewController setViewControllers:@[[wself readPageViewWithChapter:wself.chapter page:wself.page]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
});
}
}];
网上有文章说这样处理可能会crash,但是现在没有测试到,而且他的是网易新闻那种,谁试用了会crash的话可以联系我(主要这种解决方式简单,不然自己撸个轮子感觉太麻烦了);
- 小说分页
分页主要采用CoreText
中的两个方法:CTFramesetterCreateFrame
和CTFrameGetVisibleStringRange
,具体参数和作用方法里都有介绍;代码如下:
- (void)pagingWithBounds:(CGRect)bounds {
_pageArr = @[].mutableCopy;
YReaderSettings *settings = [YReaderSettings shareReaderSettings];
NSString *content = settings.isTraditional ? self.traditionalStr : self.body;
NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:content attributes:settings.readerAttributes];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attr);
CGPathRef path = CGPathCreateWithRect(bounds, NULL);
CFRange range = CFRangeMake(0, 0);
NSUInteger rangeOffset = 0;
do {
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(rangeOffset, 0), path, NULL);
range = CTFrameGetVisibleStringRange(frame);
rangeOffset += range.length;
[_pageArr addObject:@(range.location)];
if (frame) {
CFRelease(frame);
}
} while (range.location + range.length < attr.length);
if (path) {
CFRelease(path);
}
if (frameSetter) {
CFRelease(frameSetter);
}
_pageCount = _pageArr.count;
_attributedString = attr;
}
这里一个主要问题是实际显示如果和计算分页时的CGRect
相同,可能会少显示一行,处理为程序中显示的Rect
会比计算时高度增加一点,防止由于行高或者CTFrameGetVisibleStringRange
计算与实际显示之间的误差。
3.获取电池电量
[[UIDevice currentDevice] batteryLevel]
获取电池电量不准,替代方法如下:
- (double)getCurrentBatteryLevel {
UIApplication *app = [UIApplication sharedApplication];
if (app.applicationState == UIApplicationStateActive || app.applicationState==UIApplicationStateInactive) {
Ivar ivar = class_getInstanceVariable([app class],"_statusBar");
id status = object_getIvar(app, ivar);
for (id aview in [status subviews]) {
int batteryLevel = 0;
for (id bview in [aview subviews]) {
if ([NSStringFromClass([bview class]) caseInsensitiveCompare:@"UIStatusBarBatteryItemView"] == NSOrderedSame) {
Ivar ivar= class_getInstanceVariable([bview class],"_capacity");
if (ivar) {
batteryLevel = ((int (*)(id, Ivar))object_getIvar)(bview, ivar);
NSLog(@"电池电量:%zi %%",batteryLevel);
if (batteryLevel > 0 && batteryLevel <= 100) {
return batteryLevel/100.0;
}
}
}
}
}
}
return 0;
}
4.简体转繁体
这里的方法比较麻烦,拿到所有简体和繁体的文字,找出要替换文字在简体中的位置,用繁体中对应位置的文字一一替换,不知道谁有没有更好的方法;
- (NSString *)transformToTraditionalWith:(NSString *)string {
NSMutableString *mutableStr = string.mutableCopy;
NSInteger length = [string length];
for (NSInteger i = 0; i< length; i++) {
NSString *str = [string substringWithRange:NSMakeRange(i, 1)];
NSRange gbRange = [self.simplifiedStr rangeOfString:str];
if(gbRange.location != NSNotFound) {
NSString *tString = [self.traditionalStr substringWithRange:gbRange];
[mutableStr replaceCharactersInRange:NSMakeRange(i, 1) withString:tString];
}
}
return mutableStr.copy;
}
5.其他功能
本来准备GIF图,但是简书只能上传5M以下文件,详细功能可下载GitHub源码看:
项目地址:https://github.com/yanxuewen/YReaderDemo