【iOS開發】CoreText的使用(1)
本文技術點涉及到NSAttrbutedString
簡述
CoreText是用於處理文字和字型的底層技術,它直接和Core Graphics(又名Quartz2D)交流。Quartz是一個2D圖形渲染引擎,能夠處理OS X和iOS的圖形顯示問題
Quartz能夠直接處理字型(font)和字形(glyphs)將文字渲染到介面上,它是基礎庫中的唯一能夠處理字形的模組。因此,Core Text為了排版,需要將顯示的文字內容 位置 字型字形直接傳遞給Quartz。與其他UI元件相比由於Core Text直接和Quartz互動 所以它具有搞笑的排版功能。
以下是Core Text架構圖 (iOS7之後)
從這幅圖可以看出 上層UI控制元件包括UILabel UITextField和UIWebView都是基於Core Text實現的。
CoreText和UIWebView的排版比較
CoreText和UIWebView都是處理複雜文字排版的備選方案 對於排版而言 CoreText和UIWebView的比較
優點:
- CoreText佔用記憶體小,渲染速度更快 UIWebView佔用記憶體多 渲染速度慢。
- CoreText在渲染介面前就可以精確獲得顯示內容高度 (有CTFrame就行)而UIWebView只有在渲染出內容才能獲得內容的高度(通常需要和Javascript程式碼一同使用)
- CoreText的CTFrame可以在後臺執行緒渲染,UIWebView的內容只能在主執行緒(UI執行緒)渲染。
- 基於CoreText可以做更好的原生互動效果 互動效果更細膩,而UIWebView得互動效果都是用js實現,在互動效果下會有卡頓情況的存在,例如 在UIWebView下一個簡單的按鈕點選事件 和CoreText就沒辦法比
CoreText渲染出來的內容不能像UIwebView那樣方便複製
基於CoreText排版需要自己處理很多複雜的邏輯 例如需要自己處理圖片與文字混排相關邏輯,也需要自己實現連線點選操作支援。
CoreText使用情景包括:新浪微部落格戶端,多看閱讀客戶端,猿題庫。
Core Text內部結構如圖
「
上圖中大概顯示了後半部分的結構,CTFrame 是指整個該UIView子控制元件的繪製區域,CTLine則是指每一行。CTRun則是每一段具有一樣的屬性的字串 比如某段字型大小,顏色都一致的字串為一個CTRun。CTRun不可以跨行,不管屬性是否一致。通常的結構是每一個CTFrame有多個CTLine
每一個CTLine有多個CTRun。
一個簡單的不包含圖片連結等的文字顯示demo的過程如下:
- 獲取上下文
- 翻轉座標系
- 建立NSAttributedString
- 根據NSAttributedString建立CTFrameSetterRef物件
- 建立繪製區域CGPathRef
- 根據CTFramesetterRef和CGPathRef建立CTFrame
- CTFrameDraw繪製
- 手動釋放建立的CTFramesetter CTFrame CGPathRef物件。
子控制元件的drawInRect程式碼如下:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
// 1.獲取上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// [a,b,c,d,tx,ty]
NSLog(@"轉換前的座標:%@",NSStringFromCGAffineTransform(CGContextGetCTM(contextRef)));
// 2.轉換座標系,CoreText的原點在左下角,UIKit原點在左上角
CGContextSetTextMatrix(contextRef, CGAffineTransformIdentity);
// 這兩種轉換座標的方式效果一樣
// 2.1
// CGContextTranslateCTM(contextRef, 0, self.bounds.size.height);
// CGContextScaleCTM(contextRef, 1.0, -1.0);
// 2.2
CGContextConcatCTM(contextRef, CGAffineTransformMake(1, 0, 0, -1, 0, self.bounds.size.height));
NSLog(@"轉換後的座標:%@",NSStringFromCGAffineTransform(CGContextGetCTM(contextRef)));
// 3.建立繪製區域,可以對path進行個性化裁剪以改變顯示區域
CGMutablePathRef path = CGPathCreateMutable();
//CGPathAddRect(path, NULL, self.bounds);
CGPathAddEllipseInRect(path, NULL, self.bounds);
// 4.建立需要繪製的文字
NSMutableAttributedString *attributed = [[NSMutableAttributedString alloc] initWithString:@"關關雎鳩,在河之洲。窈窕淑女,君子好逑。 參差荇菜,左右流之。窈窕淑女,寤寐求之。求之不得,寤寐思服。悠哉悠哉,輾轉反側。參差荇菜,左右采之。窈窕淑女,琴瑟友之。參差荇菜,左右芼之。窈窕淑女,鐘鼓樂之。"];
[attributed addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:18] range:NSMakeRange(0, 6)];
// 兩種方式皆可
[attributed addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(3, 10)];
[attributed addAttribute:(id)kCTForegroundColorAttributeName value:[UIColor greenColor] range:NSMakeRange(0, 2)];
// 設定行距等樣式
CGFloat lineSpace = 10; // 行距一般取決於這個值
CGFloat lineSpaceMax = 20;
CGFloat lineSpaceMin = 2;
const CFIndex kNumberOfSettings = 3;
// 結構體陣列
CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
{kCTParagraphStyleSpecifierLineSpacingAdjustment,sizeof(CGFloat),&lineSpace},
{kCTParagraphStyleSpecifierMaximumLineSpacing,sizeof(CGFloat),&lineSpaceMax},
{kCTParagraphStyleSpecifierMinimumLineSpacing,sizeof(CGFloat),&lineSpaceMin}
};
CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);
// 單個元素的形式
// CTParagraphStyleSetting theSettings = {kCTParagraphStyleSpecifierLineSpacingAdjustment,sizeof(CGFloat),&lineSpace};
// CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(&theSettings, kNumberOfSettings);
// 兩種方式皆可
// [attributed addAttribute:(id)kCTParagraphStyleAttributeName value:(__bridge id)theParagraphRef range:NSMakeRange(0, attributed.length)];
// 將設定的行距應用於整段文字
[attributed addAttribute:NSParagraphStyleAttributeName value:(__bridge id)(theParagraphRef) range:NSMakeRange(0, attributed.length)];
CFRelease(theParagraphRef);
// 5.根據NSAttributedString生成CTFramesetterRef
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributed);
CTFrameRef ctFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributed.length), path, NULL);
// 6.繪製除圖片以外的部分
CTFrameDraw(ctFrame, contextRef);
// 7.記憶體管理,ARC不能管理CF開頭的物件,需要我們自己手動釋放記憶體
CFRelease(path);
CFRelease(framesetter);
CFRelease(ctFrame);
}
執行效果如下:
程式碼注意點:
- DrawInRect方法不是本文重點 不解釋
- 為什麼翻轉?座標系原點是在左下角,UIKit的座標原點在左上角。
- 建立繪製區域
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
當前是用Quartz建立了矩形加入到區域路徑中,如果替換為
CGPathAddEllipseInRect(path,NULL,self.bounds),則會發現可繪製區域變成一個橢圓。
下一篇將會涉及到圖文混排的內容。