iOS閱讀器實踐系列(一)coretext純文本排版基礎

来源:http://www.cnblogs.com/summer-blog/archive/2016/11/04/6030641.html
-Advertisement-
Play Games

前言:之前做了公司閱讀類的App,最近有時間來寫一下閱讀部分的實現過程,供梳理邏輯,計劃會寫一個系列希望能涉及到儘量多的方面與細節,歡迎大家交流、吐槽、拍磚,共同進步。 閱讀的排版用的是coretext,這篇介紹用coretext實現基本的排版功能。 關於coretext的實現原理,可以查看文檔或其 ...


前言:之前做了公司閱讀類的App,最近有時間來寫一下閱讀部分的實現過程,供梳理邏輯,計劃會寫一個系列希望能涉及到儘量多的方面與細節,歡迎大家交流、吐槽、拍磚,共同進步。

閱讀的排版用的是coretext,這篇介紹用coretext實現基本的排版功能。

關於coretext的實現原理,可以查看文檔或其他資料,這裡就不介紹了,只介紹如何應用coretext來實現一個簡單的文本排版功能。

因為coretext是離屏排版的,即在將內容渲染到屏幕之前,內容的排版工作的已經完成了。

排版過程大致過程分為 步:

一、由原始文本數據和需要的相關配置來得到屬性字元串。

二、由屬性字元串得到CTFramesetter

三、由CTFramesetter和繪製區域得到CTFrame

四、最後將CTFrame渲染到視圖的上下文中

 

1、由原始文本數據和需要的相關配置來得到屬性字元串

這一部最關鍵的是得到相關配置,這些配置可能包括文本對齊方式、段收尾縮進、行高等,下麵是一些相關配置屬性:

@interface CTFrameParserConfigure : NSObject

@property (nonatomic, assign) CGFloat frameWidth;
@property (nonatomic, assign) CGFloat frameHeight;

//字體屬性
@property (nonatomic, assign) CGFloat wordSpace;
@property (nonatomic, strong) UIColor *textColor;
@property (nonatomic, strong) NSString *fontName;
@property (nonatomic, assign) CGFloat fontSize;


//段落屬性
@property (nonatomic, assign) CGFloat lineSpace;
@property (nonatomic, assign) CTTextAlignment textAlignment; //文本對齊模式
@property (nonatomic, assign) CGFloat firstlineHeadIndent; //段首行縮進
@property (nonatomic, assign) CGFloat headIndent;  //段左側整體縮進
@property (nonatomic, assign) CGFloat tailIndent;  //段尾縮進
@property (nonatomic, assign) CTLineBreakMode lineBreakMode;  //換行模式
@property (nonatomic, assign) CGFloat lineHeightMutiple; //行高倍數器(它的值表示原行高的倍數)
@property (nonatomic, assign) CGFloat maxLineHeight; //最大行高限制(0表示無限制,是非負的,行高不能超過此值)
@property (nonatomic, assign) CGFloat minLineHeight;  //最小行高限制
@property (nonatomic,assign) CGFloat paragraphBeforeSpace;  //段前間距(相對上一段加上的間距)
@property (nonatomic, assign) CGFloat paragraphAfterSpace; //段尾間距(相對下一段加上的間距)

@property (nonatomic, assign) CTWritingDirection writeDirection;  //書寫方向

@property (nonatomic, assign) CGFloat lineSpacingAdjustment;  //The space in points added between lines within the paragraph (commonly known as leading).

@end

接下來我們要利用這些屬性,生成我們需要的配置,在我們根據我們的需要給這些屬性賦值以後,利用下麵的方法來得到我們需要的配置:

//返迴文本所有屬性的集合(以字典形式),包括字體、段落等
- (NSDictionary *)attributesWithConfig:(CTFrameParserConfigure *)config
{
    //段落屬性
    CGFloat lineSpacing = config.lineSpace;
    CGFloat firstLineIndent = config.firstlineHeadIndent;
    CGFloat lineIndent = config.headIndent;
    CGFloat tailIndent = config.tailIndent;
    CTLineBreakMode lineBreakMode = config.lineBreakMode;
    CGFloat lineHeightMutiple = config.lineHeightMutiple;
    CGFloat paragraphBeforeSpace = config.paragraphBeforeSpace;
    CGFloat paragraphAfterSpace = config.paragraphAfterSpace;
    CTWritingDirection writeDirect = config.writeDirection;
    CTTextAlignment textAlignment = config.textAlignment;
    const CFIndex kNumberOfSettings = 13;
    CTParagraphStyleSetting paragraphSettings[kNumberOfSettings] = {
        { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing },
        { kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &lineSpacing },
        { kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(CGFloat), &lineSpacing },
        { kCTParagraphStyleSpecifierAlignment, sizeof(textAlignment), &textAlignment },
        { kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(firstLineIndent), &firstLineIndent },
        { kCTParagraphStyleSpecifierHeadIndent, sizeof(lineIndent), &lineIndent },
        { kCTParagraphStyleSpecifierTailIndent, sizeof(tailIndent), &tailIndent },
        { kCTParagraphStyleSpecifierLineBreakMode, sizeof(lineBreakMode), &lineBreakMode },
        { kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(lineHeightMutiple), &lineHeightMutiple },
        { kCTParagraphStyleSpecifierLineSpacing, sizeof(lineHeightMutiple), &lineHeightMutiple },
        { kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(paragraphBeforeSpace), &paragraphBeforeSpace },
        { kCTParagraphStyleSpecifierParagraphSpacing, sizeof(paragraphAfterSpace), &paragraphAfterSpace },
        { kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(writeDirect), &writeDirect },
    };
    
    CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(paragraphSettings, kNumberOfSettings);
    
    /**
     *   字體屬性
     */
    CGFloat fontSize = config.fontSize;
    //use the postName after iOS10
//    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)config.fontName, fontSize, NULL);
    CTFontRef fontRef = CTFontCreateWithName(NULL, fontSize, NULL);
//    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"TimesNewRomanPSMT", fontSize, NULL);
   
    UIColor * textColor = config.textColor;
    
    //設置字體間距
    long number = config.wordSpace;
    CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, &number);
    
    
    NSMutableDictionary * dict = [NSMutableDictionary dictionary];
    dict[(id)kCTForegroundColorAttributeName] = (id)textColor.CGColor;
    dict[(id)kCTFontAttributeName] = (__bridge id)fontRef;
    dict[(id)kCTKernAttributeName] = (__bridge id)num;
    dict[(id)kCTParagraphStyleAttributeName] = (__bridge id)theParagraphRef;
    
    CFRelease(num);
    CFRelease(theParagraphRef);
    CFRelease(fontRef);
    return dict;
}

上述過程為先根據上面提供的段落屬性值生成段落屬性,然後生成字體、字體間距及字體顏色等屬性,然後依次將他們存入字典中。

需要註意的地方是 CTParagraphStyleSetting 為C語言的數組,需在創建時指定數組元素個數。

創建的CoreFoundation庫中的對象需要手動釋放(大部分到create方法生成的對象)

另外在系統升級到iOS10以後,在調節字體大小重新排版時,變得很慢,用Instrument查了一下,發現 

CTFontRef fontRef = CTFontCreateWithName((CFStringRef)config.fontName, fontSize, NULL);

這句代碼執行時間很長,查找資料發現是字體造成的,iOS10需要用相應的POST NAME。

 

2、由屬性字元串得到CTFramesetter

// 創建 CTFramesetterRef 實例
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)mabStr);

 

3、由CTFramesetter和繪製區域得到CTFrame

這一步的關鍵是要得到繪製的區域:

// 獲得要繪製的區域的高度
    CGSize restrictSize = CGSizeMake(viewWidth, CGFLOAT_MAX);
    CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), nil, restrictSize, nil);
    CGFloat textHeight = coreTextSize.height;

然後生成CTFrame:

//生成繪製的區域
+ (CTFrameRef)createFrameWithFramesetter:(CTFramesetterRef)framesetter frameWidth:(CGFloat)frameWidth stringRange:(CFRange)stringRange orginY:(CGFloat)originY height:(CGFloat)frameHeight
{
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, CGRectMake(0, originY, frameWidth, frameHeight)); //此時path的位置值都是coretext坐標系下的值
    
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, stringRange, path, NULL);
    
    CFRelease(frame);
    CFRelease(path);
    
    return frame;
}

這裡需要註意的地方就是代碼中註釋的地方,在排版過程中使用的坐標都是在coretext坐標系下的,即原點在屏幕左下角。

 

4、將CTFrame渲染到視圖的上下文中

這一步是要在視圖類的drawRect方法中將上步得到的CTFrame繪製出來:

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
    
    //將坐標系轉換為coretext下的坐標系
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    
    if (ctFrame != nil) 
    {
        CTFrameDraw(ctFrame, context);  
    }
}

這一步的關鍵是坐標系的轉換,因為ctFrame中包含的繪製區域是在coretext坐標系下,所以在繪製時應先將坐標系轉換為coretext坐標系再繪製,才能保證繪製位置正確。

如果渲染時需要精確到行或字體可用CTLine與CTRun,這會在後面介紹。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 隊列組 讓隊列里的任務同時執行,當任務都執行完畢時,再以通知的形式告訴程式員。舉例,同時下載兩張圖片,兩張圖片都下載完了,在合成成一張。 代碼: 日誌 效果: 如果不用隊列組,下載第一張圖片、下載第二張圖片、合併兩張圖片,就只能當做一個任務放入隊列中,不能同時下載兩張圖片,耗時幾乎多了一倍。因為,如 ...
  • 說起鏈式編程和函數式編程,小伙伴們千萬不要緊張。 聽著很高大尚,其實也就那麼回事。相信有過C#開發經驗的,或者其他編程經驗的,只要不是OC,一看就知道。 看兩行代碼: 上面的就是鏈式編程+函數式編程。 來個大白話解釋:看到括弧裡面的參數了吧,跟C的函數調用是不是很相似,包括別的語言,都用小括弧傳參, ...
  • 代理這東西,真的不想再談了,估計是個iOS開發人員都能熟練使用,跟Notification和Block一樣,都用的滾瓜爛熟了。 這裡小小的談論一下代理的擴展:隱式代理和多播代理,其實非常簡單。 隱式代理:就是定義協議的屬性時不用再遵守協議了,實現方法的類也不用在遵守協議了,因為協議方法定義在NSOb ...
  • (以下圖片在IE瀏覽器中可能無法顯示) 在開始筆記之前先加點之前記下的小知識點: UIView的常見屬性和方法: 1.@property(nonatomic,radonly)UIView *superview; 獲得自己的父控制項對象 2.@property (nonatomic,radonly,co ...
  • 動畫效果可以大大提高界面的交互效果,因此,動畫在移動開發中的應用場景較為普遍。掌握基本的動畫效果在成熟的軟體開發中不可或缺。除此之外,用戶對於動畫的接受程度遠高於文字和圖片,利用動畫效果可以加深用戶對於產品的印象。因此本文給出安卓設計中幾種常見的動畫效果。 基礎知識 在介紹安卓中的動畫效果之前,有必 ...
  • 1、簡介 Fresco是Facebook最新推出的一款用於Android應用中展示圖片的強大圖片庫,可以從網路、本地存儲和本地資源中載入圖片。相對於ImageLoader,擁有更快的圖片下載速度以及可以載入和顯示gif圖等諸多優勢,是個很好的圖片框架。 2、特點 1)記憶體管理 在5.0以下系統,Fr ...
  • 本篇記錄星級評分組件的創建過程以及CALayer的運用。 為了實現一個星級評分的組件,使用了CALayer,涉及到mask、CGPathRef、UIBezierPath、動畫和一個計算多角星關鍵節點的演算法。 CALayer管理基於圖像的內容,並讓我們可以在內容上添加動畫。UIView及其子類擁有一個 ...
  • 今天看Mansonry的代碼時,碰到一個生僻的關鍵字(也許只是自己沒用過)。:-) @encode => 將給定類型編碼為內部表示的字元串。 為了方便自己查閱,順便也寫個小例子,貼在這裡,實踐出真知嘛。 NSLog(@"UIViewController : %s", @encode(UIViewCo ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...