iOS 小說閱讀器高效分頁

廢話不多說,先上項目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,兩種方式渲染出來的效果居然不一樣。(這種情況只出現在中文,如果是全英文就沒有問題)

image

再經過一番研究后發現這是字體造成的,中文如果用[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;
}

這樣我們就可以得到每一章的字符串了,然后再開始的時候只加載一章的內容,在翻到這一章最后一頁的時候,再翻頁則加載下一章的內容。

終于最核心的分頁做完了。這樣就可以正常的閱讀了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容