CoreText 框架中最常用的幾個(gè)類
CTFont
CTFontCollection
CTFontDescriptor
CTFrame
CTFramesetter
CTGlyphInfo
CTLine
CTParagraphStyle
CTRun
CTTextTab
CTTypesetter
CoreText坐標(biāo)系
CoreText坐標(biāo)系 和 UIKit坐標(biāo)系 的不同役电,從圖中可看出 CoreText坐標(biāo)系是以左下角為坐標(biāo)原點(diǎn) 蜀踏,而我們常使用的 UIKit是以左上角為坐標(biāo)原點(diǎn) ,因此在CoreText中的布局完成后需要對(duì)其坐標(biāo)系進(jìn)行轉(zhuǎn)換吠架,否則直接繪制出現(xiàn)位置反轉(zhuǎn)的鏡像情況。在通常情況下我們一般做法是直接獲取當(dāng)前上下文蜡豹。并將當(dāng)前上下文的坐標(biāo)系轉(zhuǎn)換為CoreText坐標(biāo)系爷耀,再將布局好的CoreText繪制到當(dāng)前上下文中即可。
// 獲取當(dāng)前上下文
CGContextRef context = UIGraphicsGetCurrentContext();
// 1.設(shè)置當(dāng)前文本矩陣
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// 2.文本沿y軸移動(dòng)
CGContextTranslateCTM(context, 0, self.bounds.size.height);
// 3.文本翻轉(zhuǎn)成為CoreText坐標(biāo)系
CGContextScaleCTM(context, 1.0, -1.0);
繪制CoreText大致思路
CTFrame 作為一個(gè)整體的畫布(Canvas)绘证,其中由行(CTLine)組成,而每行可以分為一個(gè)或多個(gè)小方塊(CTRun)独柑。不需要自己創(chuàng)建CTRun迈窟,Core Text將根據(jù)NSAttributedString的屬性來(lái)自動(dòng)創(chuàng)建CTRun。每個(gè)CTRun對(duì)象對(duì)應(yīng)不同的屬性忌栅,正因此车酣,你可以自由的控制字體、顏色索绪、字間距等等信息湖员。
-
使用CoreText文本布局步聚:
1、首先要確定布局時(shí) 繪制的區(qū)域 瑞驱,其對(duì)應(yīng)的類為 CG(Mutable)PathRef娘摔。
2、設(shè)置 文本內(nèi)容 唤反,其對(duì)應(yīng)的類為 NS(Mutable)AttributedString凳寺。
3鸭津、根據(jù)文本內(nèi)容配置其 CTFramesetterRef。
4肠缨、利用CTFramesetterRef 得到CTFrame逆趋。
// 1.創(chuàng)建繪制區(qū)域,顯示的區(qū)域可以用CGMUtablePathRef生成任意的形狀
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(20, 50, self.bounds.size.width - 40, self.bounds.size.height - 100));
// 2.創(chuàng)建需要繪制的文字
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:@"文本內(nèi)容文本內(nèi)容"];
// 3.根據(jù)AttString生成CTFramesetterRef
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
// 4.利用CTFramesetterRef得到CTFrame
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, [attString length]), path, NULL);
添加點(diǎn)擊事件:CTFrame 包含了多個(gè)CTLine,并且可以得到各個(gè)line的其實(shí)位置與大小。判斷點(diǎn)擊處在不在某個(gè)line上晒奕。CTLine 又可以判斷這個(gè)點(diǎn)(相對(duì)于ctline的坐標(biāo))處的文字范圍闻书。然后遍歷這個(gè)string的所有NSTextCheckingResult,根據(jù)result的rang判斷點(diǎn)擊處在不在這個(gè)rang上脑慧,從而得到點(diǎn)擊的鏈接與位置魄眉。
說(shuō)明:CTFramesetter是由CFAttributedString(NSAttributedString)初始化而來(lái),可以認(rèn)為它是CTFrame的一個(gè)Factory闷袒,通過(guò)傳入CGPath生成相應(yīng)的CTFrame并使用它進(jìn)行渲染:直接以CTFrame為參數(shù)使用CTFrameDraw繪制或者從CTFrame中獲取CTLine進(jìn)行微調(diào)后使用CTLineDraw進(jìn)行繪制坑律。
CoreText實(shí)際上并沒(méi)有相應(yīng)API直接將一個(gè)圖片轉(zhuǎn)換為CTRun并進(jìn)行繪制,它所能做的只是為圖片預(yù)留相應(yīng)的空白區(qū)域囊骤,而真正的繪制則是交由CoreGraphics完成脾歇。
在CoreText中提供了CTRunDelegate這么個(gè)Core Foundation類,顧名思義它可以對(duì)CTRun進(jìn)行拓展:AttributedString某個(gè)段設(shè)置kCTRunDelegateAttributeName屬性之后淘捡,CoreText使用它生成CTRun是通過(guò)當(dāng)前Delegate的回調(diào)來(lái)獲取自己的ascent,descent和width池摧,而不是根據(jù)字體信息焦除。這樣就給我們留下了可操作的空間:用一個(gè)空白字符作為圖片的占位符,設(shè)好Delegate作彤,占好位置膘魄,然后用CoreGraphics進(jìn)行圖片的繪制。
字體基本知識(shí)
- 字體(Font):計(jì)算機(jī)意義上的字體表示的是同一大小竭讳,同一樣式(Style)字形的集合创葡。從這個(gè)意義上來(lái)說(shuō),當(dāng)我們?yōu)槲淖衷O(shè)置粗體绢慢,斜體時(shí)其實(shí)是使用了另外一種字體(下劃線不算)灿渴。而平時(shí)我們所說(shuō)的字體只是具有相同設(shè)計(jì)屬性的字體集合,即Font Family或typeface胰舆。
- 字符(Character)和字形(Glyphs):排版過(guò)程中一個(gè)重要的步驟就是從字符到字形的轉(zhuǎn)換骚露,字符表示信息本身,而字形是它的圖形表現(xiàn)形式缚窿。字符一般就是指某種編碼棘幸,如Unicode編碼,而字形則是這些編碼對(duì)應(yīng)的圖片倦零。但是他們之間不是一一對(duì)應(yīng)關(guān)系误续,同個(gè)字符的不同字體族吨悍,不同字體大小,不同字體樣式都對(duì)應(yīng)了不同的字形蹋嵌。
- 字形的各個(gè)參數(shù):
紅框高度既為當(dāng)前行的行高育瓜,綠線為baseline,綠色到紅框上部分為當(dāng)前行的最大Ascent欣尼,綠線到黃線為當(dāng)前行的最大Desent爆雹,而黃框的高即為行間距。由此可以得出:lineHeight = Ascent + |Decent| + Leading(行間距)愕鼓。
常用屬性介紹
// 字體形狀屬性:必須是CFNumberRef對(duì)象默認(rèn)為0钙态,非0則對(duì)應(yīng)相應(yīng)的字符形狀定義,如1表示傳統(tǒng)字符形狀
const CFStringRef kCTCharacterShapeAttributeName;
// 字體屬性:必須是CTFont對(duì)象
const CFStringRef kCTFontAttributeName;
// 字符間隔屬性:必須是CFNumberRef對(duì)象
const CFStringRef kCTKernAttributeName;
// 設(shè)置是否使用連字屬性:設(shè)置為0菇晃,表示不使用連字屬性册倒。標(biāo)準(zhǔn)的英文連字有FI,FL.默認(rèn)值為1,既是使用標(biāo)準(zhǔn)連字磺送。也就是當(dāng)搜索到f時(shí)候驻子,會(huì)把fl當(dāng)成一個(gè)文字。必須是CFNumberRef 默認(rèn)為1,可取0,1,2
const CFStringRef kCTLigatureAttributeName;
// 字體顏色屬性:必須是CGColor對(duì)象估灿,默認(rèn)為black
const CFStringRef kCTForegroundColorAttributeName;
// 上下文的字體顏色屬性:必須為CFBooleanRef 默認(rèn)為False
const CFStringRef kCTForegroundColorFromContextAttributeName;
// 段落樣式屬性:必須是CTParagraphStyle對(duì)象 默認(rèn)為NIL
const CFStringRef kCTParagraphStyleAttributeName;
// 筆畫線條寬度:必須是CFNumberRef對(duì)象崇呵,默為0.0f,標(biāo)準(zhǔn)為3.0f
const CFStringRef kCTStrokeWidthAttributeName;
// 筆畫的顏色屬性:必須是CGColorRef 對(duì)象馅袁,默認(rèn)為前景色
const CFStringRef kCTStrokeColorAttributeName;
// 設(shè)置字體的上下標(biāo)屬性:必須是CFNumberRef對(duì)象 默認(rèn)為0,可為-1為下標(biāo),1為上標(biāo)域慷,需要字體支持才行。如排列組合的樣式Cn1
const CFStringRef kCTSuperscriptAttributeName;
// 字體下劃線顏色屬性:必須是CGColorRef對(duì)象汗销,默認(rèn)為前景色
const CFStringRef kCTUnderlineColorAttributeName;
// 字體下劃線樣式屬性:必須是CFNumberRef對(duì)象,默為kCTUnderlineStyleNone 可以通過(guò)CTUnderlineStypleModifiers 進(jìn)行修改下劃線風(fēng)格
const CFStringRef kCTUnderlineStyleAttributeName;
// 文字的字形方向?qū)傩裕罕仨毷荂FBooleanRef 默認(rèn)為false犹褒,false表示水平方向,true表示豎直方向
const CFStringRef kCTVerticalFormsAttributeName;
// 字體信息屬性:必須是CTGlyphInfo對(duì)象
const CFStringRef kCTGlyphInfoAttributeName;
// CTRun 委托屬性:必須是CTRunDelegate對(duì)象
const CFStringRef kCTRunDelegateAttributeName
文本屬性設(shè)置:
// 設(shè)置繪制的文本內(nèi)容
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:@"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"];
// 設(shè)置文本內(nèi)容的屬性
// 1.設(shè)置部分文字顏色
[attString addAttribute:(id)kCTForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0 , 27)];
// 2.設(shè)置部分文字字體
CGFloat fontSize = 20;
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
[attString addAttribute:(id)kCTFontAttributeName value:(__bridge id)fontRef range:NSMakeRange(0, 27)];
// 3.設(shè)置斜體
CTFontRef italicFontRef = CTFontCreateWithName((CFStringRef)[UIFont italicSystemFontOfSize:20].fontName, 16, NULL);
[attString addAttribute:(id)kCTFontAttributeName value:(__bridge id)italicFontRef range:NSMakeRange(27, 9)];
// 4.設(shè)置下劃線
[attString addAttribute:(id)kCTUnderlineStyleAttributeName value:(id)[NSNumber numberWithInteger:kCTUnderlineStyleDouble] range:NSMakeRange(36, 10)];
// 5.設(shè)置下劃線顏色
[attString addAttribute:(id)kCTUnderlineColorAttributeName value:(id)[UIColor greenColor].CGColor range:NSMakeRange(36, 10)];
// 6.設(shè)置空心字
long number1 = 2;
CFNumberRef numRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, &number1);
[attString addAttribute:(id)kCTStrokeWidthAttributeName value:(__bridge id)numRef range:NSMakeRange(56, 10)];
// 7.設(shè)置字體間距
long number = 10;
CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, &number);
[attString addAttribute:(id)kCTKernAttributeName value:(__bridge id)num range:NSMakeRange(40, 10)];
// 8.設(shè)置行間距
CGFloat lineSpacing = 10;
const CFIndex kNumberOfSettings = 3;
CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
{kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing},
{kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &lineSpacing},
{kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &lineSpacing}
};
CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);
[attString addAttribute:(id)kCTParagraphStyleAttributeName value:(__bridge id)theParagraphRef range:NSMakeRange(0, [attString length])];
繪制圖片
Core Text本身并不支持圖片繪制弛针,圖片的繪制你還得通過(guò)Core Graphics來(lái)進(jìn)行叠骑。Core Text可以通過(guò)CTRun的設(shè)置為你的圖片在文本繪制的過(guò)程中留出適當(dāng)?shù)目臻g。這個(gè)設(shè)置就使用到CTRunDelegate削茁。CTRunDelegate作為CTRun相關(guān)屬性或操作擴(kuò)展的一個(gè)入口宙枷,使得我們可以對(duì)CTRun做一些自定義的行為。為圖片留位置的方法就是加入一個(gè)空白的CTRun茧跋,自定義其ascent朦拖,descent,width等參數(shù)厌衔,使得繪制文本的時(shí)候留下空白位置給相應(yīng)的圖片璧帝。然后圖片在相應(yīng)的空白位置上使用Core Graphics接口進(jìn)行繪制。
圖片寬高需要加載后才知道富寿,而在文本繪制中需要直接留出其位置再進(jìn)行繪制睬隶,所以圖片的寬高都是在數(shù)據(jù)中保存好的锣夹。為了留出其位置我們需要用空白的字符來(lái)做占位符使用。為了知道其圖片繪制的位置(即空白占位符位置)我們需要設(shè)置代理才能夠得知圖片繪制位置苏潜。
使用CTRunDelegateCreate可以創(chuàng)建一個(gè)CTRunDelegate银萍,它接收兩個(gè)參數(shù),一個(gè)是callbacks結(jié)構(gòu)體恤左,一個(gè)是所有callback調(diào)用的時(shí)候需要傳入的對(duì)象贴唇。 callbacks的結(jié)構(gòu)體為CTRunDelegateCallbacks,主要是包含一些回調(diào)函數(shù)飞袋,比如有返回當(dāng)前run的ascent戳气,descent,width這些值的回調(diào)函數(shù)巧鸭,至于函數(shù)中如何鑒別當(dāng)前是哪個(gè)run瓶您,可以在CTRunDelegateCreate的第二個(gè)參數(shù)來(lái)達(dá)到目的,因?yàn)镃TRunDelegateCreate的第二個(gè)參數(shù)會(huì)作為每一個(gè)回調(diào)調(diào)用時(shí)的入?yún)ⅰ?/p>
-
具體步驟:
1纲仍、創(chuàng)建 CTRunDelegateCallbacks 回調(diào)函數(shù) :通過(guò)回調(diào)函數(shù)來(lái)確定圖片繪制的寬高呀袱。
2、創(chuàng)建 空白占位字符郑叠。
3夜赵、設(shè)置 CTRunDeleagte :通過(guò)代理來(lái)找到該字符串,并確定圖片繪制的原點(diǎn)乡革。
具體實(shí)現(xiàn):
#pragma mark - CTRunDelegateCallbacks Method
static CGFloat ascentCallback(void *ref) {
return [(NSNumber *)[(__bridge NSDictionary *)ref objectForKey:@"height"] floatValue];
}
static CGFloat descentCallback(void *ref) {
return 0;
}
static CGFloat widthCallback(void *ref) {
return [(NSNumber *)[(__bridge NSDictionary *)ref objectForKey:@"width"] floatValue];
}
#pragma mark - 空白占位符及代理設(shè)置
+ (NSAttributedString *)parseImageDataFromNSDictionary:(NSDictionary *)dict config:(CTFrameParserConfig *)config {
// CTRunDelegateCallBacks:用于保存指針的結(jié)構(gòu)體油吭,由CTRun delegate進(jìn)行回調(diào)
CTRunDelegateCallbacks callbacks;
memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks));
callbacks.version = kCTRunDelegateVersion1;
callbacks.getAscent = ascentCallback;
callbacks.getDescent = descentCallback;
callbacks.getWidth = widthCallback;
// 字典中含有圖片的寬高信息
// 創(chuàng)建CTRunDelegate的代理
CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)(dict));
// 使用0xFFFC作為空白的占位符
unichar objectReplacementChar = 0xFFFC;
NSString *content = [NSString stringWithCharacters:&objectReplacementChar length:1];
NSDictionary *attributes = [self attributesWithConfig:config];
NSMutableAttributedString *space = [[NSMutableAttributedString alloc] initWithString:content attributes:attributes];
// 設(shè)置代理
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)space, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);
CFRelease(delegate);
return space;
}
#pragma mark - drawRect
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
if (self.data == nil) {
return;
}
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
if (self.state == CTDisplayViewStateTouching || self.state == CTDisplayViewStateSelecting) {
[self drawSelectionArea];
[self drawAnchors];
}
CTFrameDraw(self.data.ctFrame, context);
// 遍歷圖片數(shù)組,在適當(dāng)?shù)奈恢貌迦肜L制的圖片
for (CTImageData *imageData in self.data.imageArray) {
UIImage *image = [UIImage imageNamed:imageData.name];
if (image) {
CGContextDrawImage(context, imageData.imagePosition, image.CGImage);
}
}
}
CTRun delegate進(jìn)行回調(diào):
- (void)fillImagePosition {
if (self.imageArray.count == 0) {
return;
}
// 獲取CTLine數(shù)組
NSArray *lines = (NSArray *)CTFrameGetLines(self.ctFrame);
NSUInteger lineCount = [lines count];
CGPoint lineOrigins[lineCount];
CTFrameGetLineOrigins(self.ctFrame, CFRangeMake(0, 0), lineOrigins);
int imgIndex = 0;
CTImageData *imageData = self.imageArray[0];
//遍歷CTline
for (int i = 0; i < lineCount; ++i) {
if (imageData == nil) {
break;
}
CTLineRef line = (__bridge CTLineRef)lines[i];
NSArray *runObjArray = (NSArray *)CTLineGetGlyphRuns(line);
// 遍歷每個(gè)CTLine中的CTRun找到空白字符的delegate
for (id runObj in runObjArray) {
CTRunRef run = (__bridge CTRunRef)runObj;
NSDictionary *runAttributes = (NSDictionary *)CTRunGetAttributes(run);
CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForKey:(id)kCTRunDelegateAttributeName];
if (delegate == nil) {
continue;
}
NSDictionary *metaDic = CTRunDelegateGetRefCon(delegate);
if (![metaDic isKindOfClass:[NSDictionary class]]) {
continue;
}
// 找到代理后開始計(jì)算空白字符的位置
CGRect runBounds;
CGFloat ascent;
CGFloat descent;
runBounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
runBounds.size.height = ascent + descent;
// 計(jì)算在行當(dāng)中的x偏移量
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
runBounds.origin.x = lineOrigins[i].x + xOffset;
runBounds.origin.y = lineOrigins[i].y;
runBounds.origin.y -= descent;
// 獲得ctframe的繪制區(qū)域
CGPathRef pathRef = CTFrameGetPath(self.ctFrame);
// 計(jì)算此繪制區(qū)域的范圍
CGRect colRect = CGPathGetBoundingBox(pathRef);
// 計(jì)算在此區(qū)域中空白字符的位置
CGRect delegateBounds = CGRectOffset(runBounds, colRect.origin.x, colRect.origin.y);
// 記錄空白字符位置
imageData.imagePosition = delegateBounds;
imgIndex++;
if (imgIndex == self.imageArray.count) {
imageData = nil;
break;
} else {
imageData = self.imageArray[imgIndex];
}
}
}
}
-
幾個(gè)重要的方法:
CTFrameGetLineOrigins:該方法是獲取每一行的原點(diǎn)署拟,這個(gè)在計(jì)算字體的坐標(biāo)的時(shí)候會(huì)用到。
CTLineGetGlyphRuns:該方法獲取一行里面所有的CTRun了歌豺,因?yàn)镃TLine是由一個(gè)個(gè)CTRun組合而成的推穷。
CTRunGetAttributes:該方法獲取CTRun的一些屬性。這個(gè)方法返回的是一個(gè)字典类咧,當(dāng)然字典里面除了一些系統(tǒng)屬性外馒铃,你之前設(shè)置的一些自定義屬性也能獲取到,正是這樣我們可以通過(guò)自定義的屬性來(lái)特殊處理個(gè)別的CTRun(比如圖片痕惋,鏈接等)区宇。
CTLineGetOffsetForStringIndex:該方法是獲取具體的文字距離這行原點(diǎn)的距離,當(dāng)然也是算尺寸用的值戳。
CTLineGetStringIndexForPosition:該方法是獲取點(diǎn)擊時(shí)我們點(diǎn)擊的是哪一個(gè)文字议谷,是給點(diǎn)擊鏈接時(shí)顯示點(diǎn)擊效果用的。因?yàn)殒溄颖容^長(zhǎng)堕虹,可能會(huì)超過(guò)一行卧晓,我們需要根據(jù)點(diǎn)擊的文字找出指定的鏈接芬首,然后在整個(gè)鏈接區(qū)域繪制背景色,這個(gè)是比較復(fù)雜的部分逼裆。
Demo示例下載:https://pan.baidu.com/s/1pLhpsyn