IOS CoreText --- 代碼封裝

來源:互聯網
上載者:User

標籤:core text   core graphics   架構   封裝   

前幾節中,我轉載他人的部落格,詳細的描述了Core Text的基本概念及使用,但看上去他所提供的demo是面向過程的,代碼不容易管理及維護。接下來幾節,我將逐步封裝Core Text代碼,讓其看起來不那麼淩亂(因為Core Text是純C的文法)。下面,我們先看一張 “iOS Text Design and Rendering Architecture” 架構圖。




中,最底層的Core Graphics是核心繪畫,我在Quartz 2D章節已經進行了詳細的說明,然後上面一層的就是Core Text。 先看看我實現的一個Core Text的demo。




1. 我們先看看原始的代碼實現過程,可以看出,代碼中,將座標系的變換,路徑的初始化,字串的處理,frame的建立及最終的繪製 全部放在一起處理了,當遇到複雜的業務需求的話,代碼顯的臃腫和不利於維護。

CGContextRef context = UIGraphicsGetCurrentContext();        CGContextSetTextMatrix(context, CGAffineTransformIdentity);    CGContextTranslateCTM(context, 0, self.bounds.size.height);    CGContextScaleCTM(context, 1.0, -1.0);        CGMutablePathRef path = CGPathCreateMutable();    CGPathAddRect(path, NULL, self.bounds);        NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:@"大話西遊台詞..."];    // 這裡截斷字串,然後對每段進行文字及顏色的設定        CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrString.length), path, NULL);        CTFrameDraw(frame, context);        CFRelease(frame);    CFRelease(path);    CFRelease(framesetter);

2. 考慮對代碼進行封裝

對於一個複雜的排版引擎來說,可以將其功能拆分成以下幾個類來完成。

1)一個顯示用的類,僅負責顯示內容,不負責排版。

2)一個模型類,用於承載顯示所需要的所有資料。

3)一個排版類,用於實現文字內容的排版。

4)一個配置類,用於實現一些排版時的可配置項。

按照以上的描述,我們可以將上面的代碼內容拆分,分成4個類:

1)CTFrameParserConfig類,用於配置繪製的參數,例如文字顏色、大小、行間距等。

2)CTFrameParser類,用於產生最後繪製介面需要的CTFrameRef執行個體。

3)CoreTextData類,用於儲存由CTFrameParser類產生的CTFrameRef執行個體以及CTFrameRef實際繪製需要的高度。

4)CTDisplayView類,持有CoreTextData類的執行個體,負責將CTFrameRef繪製到介面上。


下面我就一一介紹上面說描述的類:

1)CTFrameParserConfig類,主要是初始化了文字寬度、大小、行間距、顏色資訊。 代碼如下:

@interface CTFrameParserConfig : NSObject@property (nonatomic,assign) CGFloat width;@property (nonatomic,assign) CGFloat fontSize;@property (nonatomic,assign) CGFloat lineSpace;@property (nonatomic,strong) UIColor *textColor;@end#define RGB(A,B,C) [UIColor colorWithRed:(A/255.0) green:(B/255.0) blue:(C/255.0) alpha:1.0]#import "CTFrameParserConfig.h"@implementation CTFrameParserConfig- (instancetype)init {    if (self = [super init]) {        self.width = 300.0f;        self.fontSize = 16.0f;        self.lineSpace = 8.0f;        self.textColor = RGB(108, 108, 108);    }    return  self;}@end

2)CoreTextData類,定義了兩個屬性,用來儲存繪製所需要的CTFrameRef及最終繪製的View的高度(因為高度是根據文字內容動態計算出來的)。

@interface CoreTextData : NSObject@property (nonatomic,assign) CTFrameRef ctFrame;@property (nonatomic,assign) CGFloat height;@end@implementation CoreTextData- (void)setCtFrame:(CTFrameRef)ctFrame {    if (_ctFrame != ctFrame) {        if(_ctFrame != nil) {            CFRelease(_ctFrame);        }                CFRetain(ctFrame);        _ctFrame = ctFrame;    }}- (void)dealloc {    if (_ctFrame != nil) {        CFRelease(_ctFrame);        _ctFrame = nil;    }}@end

3)CTDisplayView類, 定義了屬性data,用於接收外面傳遞進來的模型資料,然後在drwRect方法中完成繪製工作。

I. 在繪製之前先進行座標系翻轉,因為Core Text的預設座標系原點在左下角。

II.直接調用CTFrameDraw方法,完成繪製工作。

@interface CTDisplayView : UIView@property (nonatomic,strong) CoreTextData *data;@end- (void)drawRect:(CGRect)rect {    [super drawRect:rect];    CGContextRef context = UIGraphicsGetCurrentContext();    CGContextSetTextMatrix(context, CGAffineTransformIdentity);    CGContextTranslateCTM(context, 0, self.bounds.size.height);    CGContextScaleCTM(context, 1.0, -1.0);        if (self.data) {        CTFrameDraw(self.data.ctFrame, context);    }}

4)CTFrameParser類,這個類是核心類,主要完成用於產生最後繪製介面需要的CTFrameRef執行個體。先看看CTFrameParser.h檔案中的代碼:

@interface CTFrameParser : NSObject/* 對整段文字進行排版 */+ (CoreTextData *)parseContent:(NSString *)content config:(CTFrameParserConfig *)config;/* 自訂自己的排版 */+ (CoreTextData *)parseTemplateFile:(NSString *)path config:(CTFrameParserConfig *)config;@end

I. 對整段文字進行排版,直接操作傳遞過來的content字串,最終顯示出來的效果是:所有字型的大小,顏色,行間距均一致。比如:小說應用。

先來看看parseContent方法:

+ (CoreTextData *)parseContent:(NSString *)content config:(CTFrameParserConfig *)config {        NSDictionary *attributes = [self attributesWithConfig:config];    NSAttributedString *contentString = [[NSAttributedString alloc] initWithString:content attributes:attributes];        return [self parseAttributedContent:contentString config:config];}

代碼中第一句attrbutesWithConfig方法就是初始化從CTFrameParserConfig中傳遞過來的預設資訊值。

+ (NSDictionary *)attributesWithConfig:(CTFrameParserConfig *)config {    CGFloat fontSize = config.fontSize;    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);    CGFloat lineSpacing = config.lineSpace;        const CFIndex kNumberOfSettings = 3;    CTParagraphStyleSetting theSettings[kNumberOfSettings] = {        {kCTParagraphStyleSpecifierLineSpacingAdjustment,sizeof(CGFloat),&lineSpacing},        {kCTParagraphStyleSpecifierMaximumLineSpacing,sizeof(CGFloat),&lineSpacing},        {kCTParagraphStyleSpecifierMinimumLineSpacing,sizeof(CGFloat),&lineSpacing}    };    CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);        UIColor *textColor = config.textColor;        NSMutableDictionary *dict = [NSMutableDictionary dictionary];    dict[(id)kCTForegroundColorAttributeName] = (id)textColor.CGColor;    dict[(id)kCTFontAttributeName] = (__bridge id)fontRef;    dict[(id)kCTParagraphStyleAttributeName] = (__bridge id)theParagraphRef;        CFRelease(theParagraphRef);    CFRelease(fontRef);        return dict;}

代碼中的parseAttributeContent方法,就是要返回最終的CoreTextData模型資料,最終給CTDisplayView繪製使用。

+ (CoreTextData *)parseAttributedContent:(NSAttributedString *)content config:(CTFrameParserConfig *)config {    // 建立CTFramesetterRef執行個體    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)content);    // 獲得要繪製地區的高度    CGSize restrictSize = CGSizeMake(config.width, CGFLOAT_MAX);    CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), nil, restrictSize, nil);    CGFloat textHeight = coreTextSize.height;        // 產生CTFrameRef執行個體    CTFrameRef frame = [self createFrameWithFramesetter:framesetter config:config height:textHeight];        // 將產生好的CTFrameRef執行個體和計算好的繪製高度儲存到CoreTextData執行個體中,並返回    CoreTextData *data = [[CoreTextData alloc] init];    data.ctFrame = frame;    data.height = textHeight;        // 記憶體釋放    CFRelease(frame);    CFRelease(framesetter);        return data;}

II.自訂排版,正如我文章開頭貼出來的效果。

/* 自訂自己的排版 */+ (CoreTextData *)parseTemplateFile:(NSString *)path config:(CTFrameParserConfig *)config {    NSAttributedString *content = [self loadTemplateFile:path config:config];    return [self parseAttributedContent:content config:config];}

先來看看loadTempllateFile方法,就是用來載入資料來源,在“對整段文字進行排版”,資料來源就是content字串;而現在這種情況,資料來源是自訂的了,它是plist,json等資料形式。為了示範方便,我這裡採取的是plist形式。準備的plist結構大致如下:



loadTemplateFile方法具體實現如下:

+ (NSAttributedString *)loadTemplateFile:(NSString *)path config:(CTFrameParserConfig *)config {    NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init];    // JSON方式擷取資料    //        NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];    NSArray *array = [NSArray arrayWithContentsOfFile:path];    if (array) {        if ([array isKindOfClass:[NSArray class]]) {            for (NSDictionary *dict in array) {                NSAttributedString *as = [self parseAttributedContentFromNSDictionary:dict config:config];                [result appendAttributedString:as];            }        }    }    return result;}

可以看出,代碼中,遍曆plist檔案中的每一個字典資料,然後再調用parseAttributedContentFromNSDictionary方法進行文字的具體處理,這樣就可以保證,每段文字的風格不一致了,以達到自訂的效果。parseAttributedContentFromNSDictionary方法代碼如下:

+ (NSAttributedString *)parseAttributedContentFromNSDictionary:(NSDictionary *)dict config:(CTFrameParserConfig *)config {    NSMutableDictionary *attributes = (NSMutableDictionary *)[self attributesWithConfig:config];        UIColor *color = [self colorFromTemplate:dict[@"color"]];    if (color) {        attributes[(id)kCTForegroundColorAttributeName] = (id)color.CGColor;    }        CGFloat fontSize = [dict[@"size"] floatValue];    if (fontSize > 0) {        CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);        attributes[(id)kCTFontAttributeName] = (__bridge id)(fontRef);        CFRelease(fontRef);    }        NSString *content = dict[@"content"];    return [[NSAttributedString alloc] initWithString:content attributes:attributes];}+ (UIColor *)colorFromTemplate:(NSString *)name {    if ([name isEqualToString:@"blue"]) {        return [UIColor blueColor];    } else if ([name isEqualToString:@"green"]) {        return [UIColor greenColor];    } else if ([name isEqualToString:@"red"]) {        return [UIColor redColor];    } else if ([name isEqualToString:@"purple"]) {        return [UIColor purpleColor];    } else {        return nil;    }}

至此,所有類的定義及實現全部完成了,最終我們調用的代碼如下:

CTFrameParserConfig *config = [[CTFrameParserConfig alloc] init];    config.width = self.ctView.width;        NSString *path = [[NSBundle mainBundle] pathForResource:@"TempData.plist" ofType:nil];    CoreTextData *data = [CTFrameParser parseTemplateFile:path config:config];    // 傳遞資料給CTDisplayView,然後繪製內容    self.ctView.data = data;    // 設定CTDisplayView的高度    self.ctView.height = data.height;

看到上面的代碼,是不是瞬間覺得簡單易讀,並且屏蔽了那些枯燥無味的C語言介面。所以我們想實現自訂模板,只要提供好plist或者json檔案的資料形式及內容。


是架構的UML:



1. CTFrameParser通過CTFrameParserConfig執行個體來產生CoreTextData執行個體。

2. CTDisplayView通過持有CoreTextData執行個體來獲得繪製所需要的所有資訊。

3. ViewController類通過配置CTFrameParserConfig執行個體,進而獲得產生的CoreTextData執行個體,最後將其賦值給它的CTDisplayView成員,達到將指定內容顯示在介面上得效果。

IOS CoreText --- 代碼封裝

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.