廢話不多說,先上項目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 平方-繁
這里參考了http://www.lxweimin.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;
}
這樣我們就可以得到每一章的字符串了,然后再開始的時候只加載一章的內容,在翻到這一章最后一頁的時候,再翻頁則加載下一章的內容。
終于最核心的分頁做完了。這樣就可以正常的閱讀了。