系列文章:
- CoreText實(shí)現(xiàn)圖文混排
- CoreText實(shí)現(xiàn)圖文混排之點(diǎn)擊事件
- CoreText實(shí)現(xiàn)圖文混排之文字環(huán)繞及點(diǎn)擊算法
- CoreText實(shí)現(xiàn)圖文混排之尺寸估算及文本選擇
也好久沒來寫博客了,主要是最近也工作了,手頭的事有點(diǎn)多,一時(shí)間也就斷了,閑下來了我就來補(bǔ)博客了碌更,剛好最近也做了很多東西,放在這里也算給自己做個(gè)筆記吧。
2016.12.25補(bǔ)充:最新demo在第三篇文章末尾宪赶。
CoreText
最近公司做了一個(gè)項(xiàng)目,需要用到圖文混排
技術(shù)脯燃。于是呢就瘋狂地在網(wǎng)上搜刮資料搂妻。
不過很不幸的是,百度的CoreText資料還是比較少滴辕棚,翻來覆去就那幾個(gè)版本欲主。
然而我又上不去谷歌,so逝嚎,困難重重啊扁瓢。
不過雖然資料少,不夠前輩們給的貢獻(xiàn)終于還是在我的努力下都被我消化了懈糯,然后我也來做個(gè)筆記涤妒。
CoreText的介紹
Core Text 是基于 iOS 3.2+ 和 OSX 10.5+ 的一種能夠?qū)ξ谋靖袷胶臀谋静季诌M(jìn)行精細(xì)控制的文本引擎。
它良好的結(jié)合了 UIKit 和 Core Graphics/Quartz:
UIKit 的 UILabel 允許你通過在 IB 中簡(jiǎn)單的拖曳添加文本赚哗,但你不能改變文本的顏色和其中的單詞她紫。
Core Graphics/Quartz幾乎允許你做任何系統(tǒng)允許的事情,但你需要為每個(gè)字形計(jì)算位置屿储,并畫在屏幕上贿讹。
Core Text 正結(jié)合了這兩者!你可以完全控制位置够掠、布局民褂、類似文本大小和顏色這樣的屬性,而 Core Text 將幫你完善其它的東西——類似文本換行、字體呈現(xiàn)等等赊堪。
以上就是對(duì)CoreText的介紹面殖。
老司機(jī)對(duì)CoreText實(shí)現(xiàn)圖文混排的一些理解
老司機(jī)認(rèn)為,圖文混排中使用到的CoreText只是CoreText龐大體系中一個(gè)對(duì)富文本的增強(qiáng)的一部分哭廉。
我個(gè)人想法啊脊僚,我讀書少,理解的可能不到位遵绰,不過你咬我啊辽幌。
恩,我又逗逼了一波椿访,說好的大師氣質(zhì)呢乌企,下面開始嚴(yán)肅了啊。
嚴(yán)肅的就是iOS7新推出的類庫(kù)Textkit
成玫,其實(shí)是在之前推出的CoreText上的封裝
加酵,根據(jù)蘋果的說法,他們開發(fā)了兩年多才完成梁剔,而且他們?cè)陂_發(fā)時(shí)候也將表情混排作為一個(gè)使用案例進(jìn)行研究虽画,所以要實(shí)現(xiàn)表情混排將會(huì)非常容易
。
蘋果引入TextKit的目的并非要取代已有的CoreText框架荣病,雖然CoreText的主要作用也是用于文字的排版和渲染
码撰,但它是一種先進(jìn)而又處于底層技術(shù),如果我們需要將文本內(nèi)容直接渲染到圖形上下文(Graphics context)時(shí)个盆,從性能和易用性來考慮
脖岛,最佳方案就是使用CoreText
。
原理的東西學(xué)一學(xué)總沒有壞處颊亮。因此柴梆,還是有必要去學(xué)一學(xué)CoreText的。
那我們開始學(xué)習(xí)吧终惑。
富文本
老司機(jī)說過绍在,我要講的只是用來增強(qiáng)富文本的那一部分,那么富文本怎么使用呢雹有。
富文本是什么呢偿渡?
富文本格式(RTF)規(guī)范是為了便于在應(yīng)用程序之間輕松轉(zhuǎn)儲(chǔ)格式化文本和圖形的一種編碼方法。
現(xiàn)在霸奕,用戶可以利用特定轉(zhuǎn)換軟件溜宽,在不同系統(tǒng)如MS-DOS、Windows质帅、OS/2适揉、Macintosh和Power Macintosh的應(yīng)用程序之間轉(zhuǎn)移字處理文檔留攒。
RTF規(guī)范提供一種在不同的輸出設(shè)備、操作環(huán)境和操作系統(tǒng)之間交換文本和圖形的一種格式嫉嘀。
RTF使用ANSI, PC-8, Macintosh, 或IBM PC字符集控制文檔的表示法和格式化炼邀,包括屏幕顯示和打印
。憑借RTF規(guī)范吃沪,不同的操作系統(tǒng)和不同的軟件程序創(chuàng)建的文檔能夠在這些操作系統(tǒng)和應(yīng)用程序之間傳遞汤善。
將一個(gè)格式化的文件轉(zhuǎn)換為RTF文件的軟件稱為RTF書寫器。
RTF書寫器用于分離現(xiàn)有文本中的程序控制信息票彪,并且生成一個(gè)包含文本和與之相關(guān)的RTF組的新文件。
將RTF文件轉(zhuǎn)換成格式化文件的軟件則稱為RTF閱讀器不狮。
簡(jiǎn)單的說降铸,附帶有每一個(gè)文字屬性的字符串
,就是富文本摇零。
在iOS中推掸,我們有一個(gè)專門的類來處理富文本 AttributeString
。
富文本的基本使用方法
誒驻仅,標(biāo)題越來越小了谅畅,都4個(gè)#號(hào)了,說明我扯遠(yuǎn)了啊噪服。不過要想使用CoreText不會(huì)富文本還是不行啊毡泻。
來吧。
AttributedString也分為NSAttributedString
和NSMutableAttributedString
兩個(gè)類粘优,類似于String仇味,我就不贅述了。
富文本本質(zhì)上沒有什么難度雹顺,只要給指定的字符串附上指定的屬性就好了丹墨。下面給出富文本的一些基本方法。
- -initWithString:
以NSString初始化一個(gè)富文本對(duì)象
- -setAttributes:range:
為富文本中的一段范圍添加一些屬性
嬉愧,第一個(gè)參數(shù)是個(gè)NSDictionary字典贩挣,第二個(gè)參數(shù)是NSRange。 - -addAttribute:value:range:
添加一個(gè)屬性
- -addAttributes:range:
添加多個(gè)屬性
- -removeAttribute:range:
移除屬性
額,老司機(jī)知道這么說不直觀没酣,來來來王财,上代碼。
NSDictionary * dic = @{NSFontAttributeName:[UIFont fontWithName:@"Zapfino" size:20],NSForegroundColorAttributeName:[UIColor redColor],NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle)};
NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:@"0我是一個(gè)富文本四康,9聽說我有很多屬性搪搏,19I will try。32這里清除屬性."];
// 設(shè)置屬性
[attributeStr setAttributes:dic range:NSMakeRange(0, attributeStr.length)];
// 添加屬性
[attributeStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:30] range:NSMakeRange(9, 10)];
[attributeStr addAttribute:NSForegroundColorAttributeName value:[UIColor cyanColor] range:NSMakeRange(13, 13)];
// 添加多個(gè)屬性
NSDictionary * dicAdd = @{NSBackgroundColorAttributeName:[UIColor yellowColor],NSLigatureAttributeName:@1};
[attributeStr addAttributes:dicAdd range:NSMakeRange(19, 13)];
// 移除屬性
[attributeStr removeAttribute:NSFontAttributeName range:NSMakeRange(32, 9)];
UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 400)];
label.numberOfLines = 0;
label.attributedText = attributeStr;
這里你要注意一下闪金,給label的一定是給他的attributedText
屬性疯溺,你給text是不行的论颅。
是不是用起來很簡(jiǎn)單,富文本囱嫩,跟字典沒什么區(qū)別么恃疯。
CoreText繪制富文本
是不是終于進(jìn)入正題了。其實(shí)之所以說那么多墨闲,還是為了你看完就能保證會(huì)用啊今妄,否則你不會(huì)富文本你自己還要查找富文本相關(guān)資料。
Come On鸳碧!
CoreText實(shí)現(xiàn)圖文混排其實(shí)就是在富文本中插入一個(gè)空白的圖片占位符
的富文本字符串盾鳞,通過代理設(shè)置相關(guān)的圖片尺寸信息
,根據(jù)從富文本得到的frame計(jì)算圖片繪制的frame再繪制圖片
這么一個(gè)過程瞻离。
先來整體代碼
-(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);
NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:@"\n這里在測(cè)試圖文混排腾仅,\n我是一個(gè)富文本"];
CTRunDelegateCallbacks callBacks;
memset(&callBacks,0,sizeof(CTRunDelegateCallbacks));
callBacks.version = kCTRunDelegateVersion1;
callBacks.getAscent = ascentCallBacks;
callBacks.getDescent = descentCallBacks;
callBacks.getWidth = widthCallBacks;
NSDictionary * dicPic = @{@"height":@129,@"width":@400};
CTRunDelegateRef delegate = CTRunDelegateCreate(& callBacks, (__bridge void *)dicPic);
unichar placeHolder = 0xFFFC;
NSString * placeHolderStr = [NSString stringWithCharacters:&placeHolder length:1];
NSMutableAttributedString * placeHolderAttrStr = [[NSMutableAttributedString alloc] initWithString:placeHolderStr];
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderAttrStr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);
CFRelease(delegate);
[attributeStr insertAttributedString:placeHolderAttrStr atIndex:12];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
NSInteger length = attributeStr.length;
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, length), path, NULL);
CTFrameDraw(frame, context);
UIImage * image = [UIImage imageNamed:@"bd_logo1"];
CGRect imgFrm = [self calculateImageRectWithFrame:frame];
CGContextDrawImage(context,imgFrm, image.CGImage);
CFRelease(frame);
CFRelease(path);
CFRelease(frameSetter);
}
static CGFloat ascentCallBacks(void * ref)
{
return [(NSNumber *)[(__bridge NSDictionary *)ref valueForKey:@"height"] floatValue];
}
static CGFloat descentCallBacks(void * ref)
{
return 0;
}
static CGFloat widthCallBacks(void * ref)
{
return [(NSNumber *)[(__bridge NSDictionary *)ref valueForKey:@"width"] floatValue];
}
-(CGRect)calculateImageRectWithFrame:(CTFrameRef)frame
{
NSArray * arrLines = (NSArray *)CTFrameGetLines(frame);
NSInteger count = [arrLines count];
CGPoint points[count];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), points);
for (int i = 0; i < count; i ++) {
CTLineRef line = (__bridge CTLineRef)arrLines[i];
NSArray * arrGlyphRun = (NSArray *)CTLineGetGlyphRuns(line);
for (int j = 0; j < arrGlyphRun.count; j ++) {
CTRunRef run = (__bridge CTRunRef)arrGlyphRun[j];
NSDictionary * attributes = (NSDictionary *)CTRunGetAttributes(run); CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[attributes valueForKey:(id)kCTRunDelegateAttributeName];
if (delegate == nil) {
continue;
}
NSDictionary * dic = CTRunDelegateGetRefCon(delegate);
if (![dic isKindOfClass:[NSDictionary class]]) {
continue;
}
CGPoint point = points[i];
CGFloat ascent;
CGFloat descent;
CGRect boundsRun;
boundsRun.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
boundsRun.size.height = ascent + descent;
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
boundsRun.origin.x = point.x + xOffset;
boundsRun.origin.y = point.y - descent;
CGPathRef path = CTFrameGetPath(frame);
CGRect colRect = CGPathGetBoundingBox(path);
CGRect imageBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);
return imageBounds;
}
}
return CGRectZero;
}
不瞞你說,我看著代碼都煩套利,也怕推励,所以放心,老司機(jī)會(huì)一句一句給你解釋的肉迫。
分段解析
準(zhǔn)備工作
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
先要來一個(gè)背景介紹哈
/*
coreText 起初是為OSX設(shè)計(jì)的验辞,而OSX得坐標(biāo)原點(diǎn)是左下角,y軸正方向朝上喊衫。iOS中坐標(biāo)原點(diǎn)是左上角跌造,y軸正方向向下。
若不進(jìn)行坐標(biāo)轉(zhuǎn)換格侯,則文字從下開始鼻听,還是倒著的
如下圖(盜的圖,別打我)
*/
這四句什么意思呢联四?
首先第一句撑碴。
CGContextRef context = UIGraphicsGetCurrentContext();//獲取當(dāng)前繪制上下文
為什么要回去上下文呢?因?yàn)槲覀兯械睦L制操作都是在上下文上進(jìn)行繪制的朝墩。
然后剩下的三句醉拓。
CGContextSetTextMatrix(context, CGAffineTransformIdentity);//設(shè)置字形的變換矩陣為不做圖形變換
CGContextTranslateCTM(context, 0, self.bounds.size.height);//平移方法,將畫布向上平移一個(gè)屏幕高
CGContextScaleCTM(context, 1.0, -1.0);//縮放方法收苏,x軸縮放系數(shù)為1亿卤,則不變,y軸縮放系數(shù)為-1鹿霸,則相當(dāng)于以x軸為軸旋轉(zhuǎn)180度
正如之上的背景說的排吴,coreText使用的是系統(tǒng)坐標(biāo)
,然而我們平時(shí)所接觸的iOS的都是屏幕坐標(biāo)
懦鼠,所以要將屏幕坐標(biāo)系轉(zhuǎn)換
系統(tǒng)坐標(biāo)系钻哩,這樣才能與我們想想的坐標(biāo)互相對(duì)應(yīng)屹堰。
事實(shí)上呢,這三句是翻轉(zhuǎn)畫布的固定寫法街氢,這三句你以后會(huì)經(jīng)吵都看到的。
繼續(xù)珊肃。
圖片的代理的設(shè)置
/*
事實(shí)上荣刑,圖文混排就是在要插入圖片的位置插入一個(gè)富文本類型的占位符。通過CTRUNDelegate設(shè)置圖片
*/
NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:@"\n這里在測(cè)試圖文混排伦乔,\n我是一個(gè)富文本"];//這句不用我多說吧厉亏,最起碼得有個(gè)富文本啊才能插入不是。
/*
設(shè)置一個(gè)回調(diào)結(jié)構(gòu)體烈和,告訴代理該回調(diào)那些方法
*/
CTRunDelegateCallbacks callBacks;//創(chuàng)建一個(gè)回調(diào)結(jié)構(gòu)體叶堆,設(shè)置相關(guān)參數(shù)
memset(&callBacks,0,sizeof(CTRunDelegateCallbacks));//memset將已開辟內(nèi)存空間 callbacks 的首 n 個(gè)字節(jié)的值設(shè)為值 0, 相當(dāng)于對(duì)CTRunDelegateCallbacks內(nèi)存空間初始化
callBacks.version = kCTRunDelegateVersion1;//設(shè)置回調(diào)版本,默認(rèn)這個(gè)
callBacks.getAscent = ascentCallBacks;//設(shè)置圖片頂部距離基線的距離
callBacks.getDescent = descentCallBacks;//設(shè)置圖片底部距離基線的距離
callBacks.getWidth = widthCallBacks;//設(shè)置圖片寬度
注意了斥杜,這里經(jīng)GreyLove提醒有重要改動(dòng),就是這里沥匈,添加了memset蔗喂,碼字的時(shí)候少忘碼了一句話。怪我粗心高帖,十分抱歉缰儿,我會(huì)通知每一個(gè)留言的同學(xué)。
為什么要設(shè)置一個(gè)回調(diào)結(jié)構(gòu)體呢散址?
因?yàn)閏oreText中大量的調(diào)用c的方法乖阵。事實(shí)上你會(huì)發(fā)現(xiàn)大部分跟系統(tǒng)底層有關(guān)的都需要調(diào)c的方法。
所以設(shè)置代理要按照人家的方法來啊预麸。
看看這幾句代碼也很好懂瞪浸,就是注釋中寫的意思。
后三句分別就是說當(dāng)我需要走這些代理的時(shí)候都會(huì)走那些代理方法
吏祸。
好吧对蒲,扯到這又要補(bǔ)充知識(shí)了。這個(gè)距離什么東西呢贡翘?
對(duì)對(duì)蹈矮,這呢就是一個(gè)CTRun的尺寸圖,什么你問CTRun是啥鸣驱?還沒到那呢泛鸟,后面會(huì)詳細(xì)介紹。
在這你只要知道踊东,一會(huì)我們繪制圖片的時(shí)候?qū)嶋H上實(shí)在一個(gè)CTRun中繪制這個(gè)圖片北滥,那么CTRun繪制的坐標(biāo)系中刚操,他會(huì)以origin點(diǎn)作為原點(diǎn)
進(jìn)行繪制。
基線為過原點(diǎn)的x軸
碑韵,ascent即為CTRun頂線距基線的距離
赡茸,descent即為底線距基線的距離
。
我們繪制圖片應(yīng)該從原點(diǎn)開始繪制祝闻,圖片的高度及寬度及CTRun的高度及寬度占卧,我們通過代理設(shè)置CTRun的尺寸間接設(shè)置圖片的尺寸。
/*
創(chuàng)建一個(gè)代理
*/
NSDictionary * dicPic = @{@"height":@129,@"width":@400};//創(chuàng)建一個(gè)圖片尺寸的字典联喘,初始化代理對(duì)象需要
CTRunDelegateRef delegate = CTRunDelegateCreate(& callBacks, (__bridge void *)dicPic);//創(chuàng)建代理
上面只是設(shè)置了回調(diào)結(jié)構(gòu)體华蜒,然而我們還沒有告訴這個(gè)代理我們要的圖片尺寸
。
所以這句話就在設(shè)置代理的時(shí)候綁定了一個(gè)返回圖片尺寸的字典
豁遭。
事實(shí)上此處你可以綁定任意對(duì)象
叭喜。此處你綁定的對(duì)象既是回調(diào)方法中的參數(shù)ref
。
好吧就然說到這我就直接把那三個(gè)回調(diào)方法說了吧蓖谢,放在一起比較好理解一些捂蕴。
static CGFloat ascentCallBacks(void * ref)
{
return [(NSNumber *)[(__bridge NSDictionary *)ref valueForKey:@"height"] floatValue];
}
static CGFloat descentCallBacks(void * ref)
{
return 0;
}
static CGFloat widthCallBacks(void * ref)
{
return [(NSNumber *)[(__bridge NSDictionary *)ref valueForKey:@"width"] floatValue];
}
上文說過,ref既是創(chuàng)建代理是綁定的對(duì)象闪幽。所以我們?cè)谶@里啥辨,從字典中分別取出圖片的寬和高
。
值得注意的是盯腌,由于是c的方法溉知,所以也沒有什么對(duì)象的概念。是一個(gè)指針類型的數(shù)據(jù)
腕够。不過oc的對(duì)象其實(shí)也就是c的結(jié)構(gòu)體级乍。我們可以通過類型轉(zhuǎn)換獲得oc中的字典。
__bridge既是C的結(jié)構(gòu)體轉(zhuǎn)換成OC對(duì)象時(shí)需要的一個(gè)修飾詞
帚湘。
老司機(jī)敲字慢啊玫荣,敲到這都兩個(gè)小時(shí)了,容我喝口水客们。
你們喝過紅色的尖叫么崇决?老司機(jī)喝了那種煙頭泡的水之后精神滿滿
的繼續(xù)敲字。(那水超難喝底挫,你可以挑戰(zhàn)一下)
誒恒傻,說好的嚴(yán)肅呢?
圖片的插入
首先創(chuàng)建一個(gè)富文本類型的圖片占位符建邓,綁定我們的代理
unichar placeHolder = 0xFFFC;//創(chuàng)建空白字符
NSString * placeHolderStr = [NSString stringWithCharacters:&placeHolder length:1];//已空白字符生成字符串
NSMutableAttributedString * placeHolderAttrStr = [[NSMutableAttributedString alloc] initWithString:placeHolderStr];//用字符串初始化占位符的富文本
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderAttrStr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);//給字符串中的范圍中字符串設(shè)置代理
CFRelease(delegate);//釋放(__bridge進(jìn)行C與OC數(shù)據(jù)類型的轉(zhuǎn)換盈厘,C為非ARC,需要手動(dòng)管理)
這里富文本的知識(shí)上文中已經(jīng)介紹過了官边。不過老司機(jī)猜你有三個(gè)疑問沸手。
- 這個(gè)添加屬性的方法怎么是這個(gè)樣子的外遇?
因?yàn)檫@里是添加CTRunDelegate這種數(shù)據(jù)類型,要用CoreText專門的方法契吉,不過其實(shí)就是形式不同跳仿,作用一樣的。 - 為什么這里富文本類型轉(zhuǎn)換的時(shí)候不用_bridge呢捐晶?老司機(jī)你不是說需要修飾詞么菲语?你是不是騙我?(markDown語(yǔ)法沖突我少打一個(gè)下劃線)
真沒有惑灵,事實(shí)上不是所有數(shù)據(jù)轉(zhuǎn)換的時(shí)候都需要__bridge山上。你要問我怎么區(qū)分?那好我告訴你英支,C中就是傳遞指針的數(shù)據(jù)就不用佩憾。比如說字符串,數(shù)組干花。原因老司機(jī)現(xiàn)在解釋不通妄帘,等我能組織好語(yǔ)言的。 - 為什么還要釋放池凄?我是ARC環(huán)境啊
不好意思寄摆,我也是。不過為什么要釋放呢修赞?因?yàn)槟氵M(jìn)行了類型轉(zhuǎn)換之后就不屬于對(duì)象了,也不再歸自動(dòng)引用計(jì)數(shù)機(jī)制管理了桑阶,所以你得手動(dòng)管理
咯柏副。
然后將占位符插入到我們的富文本中
[attributeStr insertAttributedString:placeHolderAttrStr atIndex:12];//將占位符插入原富文本
此處我就不贅述了,富文本的知識(shí)你只要類比字典就好了蚣录。
至此割择,我們已經(jīng)生成好了我們要的帶有圖片信息的富文本了,接下來我們只要在畫布上繪制
出來這個(gè)富文本就好了萎河。
繪制
繪制呢荔泳,又分成兩部分,繪制文本
和繪制圖片
虐杯。你問我為什么還分成兩個(gè)玛歌?
因?yàn)楦晃谋局心闾砑拥膱D片只是一個(gè)帶有圖片尺寸的空白占位符
啊,你繪制的時(shí)候他只會(huì)繪制出相應(yīng)尺寸的空白占位符擎椰,所以什么也顯示不了啊支子。
那怎么顯示圖片啊达舒?拿到占位符的坐標(biāo)值朋,在占位符的地方繪制相應(yīng)大小的圖片
就好了叹侄。恩,說到這昨登,圖文混排的原理已經(jīng)說完了趾代。
先來繪制文本吧。
繪制文本
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr);//一個(gè)frame的工廠丰辣,負(fù)責(zé)生成frame
CGMutablePathRef path = CGPathCreateMutable();//創(chuàng)建繪制區(qū)域
CGPathAddRect(path, NULL, self.bounds);//添加繪制尺寸
NSInteger length = attributeStr.length;
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0,length), path, NULL);//工廠根據(jù)繪制區(qū)域及富文本(可選范圍撒强,多次設(shè)置)設(shè)置frame
CTFrameDraw(frame, context);//根據(jù)frame繪制文字
frameSetter是根據(jù)富文本生成的一個(gè)frame生成的工廠,你可以通過framesetter
以及你想要繪制的富文本的范
圍獲取該CTRun的frame
糯俗。
但是你需要注意的是尿褪,獲取的frame是僅繪制你所需要的那部分富文本的frame
。即當(dāng)前情況下得湘,你繪制范圍定為(10杖玲,1),那么你得到的尺寸是只繪制(10淘正,1)的尺寸摆马,他應(yīng)該從屏幕左上角開始(因?yàn)槟愀淖兞俗鴺?biāo)系),而不是當(dāng)你繪制全部富文本時(shí)他該在的位置
鸿吆。
然后建立一會(huì)繪制的尺寸囤采,實(shí)際上就是在指定你的繪制范圍
。
接著生成整個(gè)富文本繪制所需要的frame
惩淳。因?yàn)榉秶侨课谋窘短海垣@取的frame即為全部文本的frame(此處老司機(jī)希望你一定要搞清楚全部與指定范圍獲取的frame他們都是從左上角開始的,否則你會(huì)進(jìn)入一個(gè)奇怪的誤區(qū)思犁,稍后會(huì)提到的)代虾。
最后,根據(jù)你獲得的frame激蹲,繪制全部富文本
棉磨。
繪制圖片
上面你已經(jīng)繪制出文字,不過沒有圖片哦学辱,接下來繪制圖片乘瓤。
繪制圖片用下面這個(gè)方法,通用的哦
CGContextDrawImage(context,imgFrm, image.CGImage);//繪制圖片
我們可以看到這個(gè)方法有三個(gè)參數(shù)策泣,分別是context衙傀,frame,以及image
萨咕。
要什么就給他什么好咯差油,context和image都好說,context就是當(dāng)前的上下文,最開始獲得那個(gè)蓄喇。image就是你要添加的那個(gè)圖片发侵,不過是CGImage類型
。通過UIImage轉(zhuǎn)出CGImage就好了妆偏,我們重點(diǎn)講一下frame的獲取刃鳄。
frame的獲取
記得我之前說的誤區(qū)么?這里我們要獲得Image的frame钱骂,你有沒有想過我們的frameSetter叔锐?
我也想過,不過就像我說的见秽,你單獨(dú)用frameSetter求出的image的frame是不正確的愉烙,那是只繪制image而得的坐標(biāo)箱叁,所以哪種方法不能用哦笼才,要用下面的方法。
你們一定發(fā)現(xiàn)鹅搪,我獲取frame的方法單獨(dú)寫了一個(gè)方法禀苦,為什么呢蔓肯?
1.將代碼分離
,方便修改振乏。
2.最主要的是這部分代碼到哪里都能用蔗包,達(dá)到復(fù)用
效果。
NSArray * arrLines = (NSArray *)CTFrameGetLines(frame);//根據(jù)frame獲取需要繪制的線的數(shù)組
NSInteger count = [arrLines count];//獲取線的數(shù)量
CGPoint points[count];//建立起點(diǎn)的數(shù)組(cgpoint類型為結(jié)構(gòu)體慧邮,故用C語(yǔ)言的數(shù)組)
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), points);//獲取起點(diǎn)
第一句呢调限,獲取繪制frame中的所有CTLine。CTLine误澳,又不知道了吧旧噪,老司機(jī)又要無恥的盜圖了。
上面呢脓匿,我們能看到一個(gè)CTFrame繪制的原理。
- CTLine 可以看做Core Text繪制中的一行的對(duì)象 通過它可以獲得當(dāng)前行的line ascent,line descent ,line leading,還可以獲得Line下的所有Glyph Runs
- CTRun 或者叫做 Glyph Run宦赠,是一組共享想相同attributes(屬性)的字形的集合體
一個(gè)CTFrame有幾個(gè)CTLine組成陪毡,有幾行文字就有幾行CTLine。一個(gè)CTLine有包含多個(gè)CTRun勾扭,一個(gè)CTRun是所有屬性都相同的那部分富文本的繪制單元毡琉。所以CTRun是CTFrame的基本繪制單元
。
接著說我們的代碼妙色。
為什么我獲取的數(shù)組需要進(jìn)行類型轉(zhuǎn)換呢桅滋?因?yàn)镃TFrameGetLines()返回值是CFArrayRef類型
的數(shù)據(jù)。就是一個(gè)c的數(shù)組類型吧,暫且先這么理解丐谋,所以需要轉(zhuǎn)換芍碧。
那為什么不用__bridge呢?記得么号俐,我說過泌豆,本身就傳地址的數(shù)據(jù)是不用橋接的
。就是這樣吏饿。
然后獲取數(shù)組的元素個(gè)數(shù)踪危。有什么用呢,因?yàn)槲覀円玫矫總€(gè)CTLine的原點(diǎn)坐標(biāo)
進(jìn)行計(jì)算猪落。每個(gè)CTLine都有自己的origin贞远。所以要生成一個(gè)相同元素個(gè)數(shù)的數(shù)組去盛放origin對(duì)象
。
然后用CTFrameGetLineOrigins獲取所有原點(diǎn)笨忌。
到此蓝仲,我們計(jì)算frame的準(zhǔn)備工作完成了。才完成準(zhǔn)備工作蜜唾。
計(jì)算frame
思路呢杂曲,就是遍歷
我們的frame中的所有CTRun,檢查
他是不是我們綁定圖片的那個(gè)袁余,如果是擎勘,根據(jù)該CTRun所在CTLine的origin以及CTRun在CTLine中的橫向偏移量計(jì)算
出CTRun的原點(diǎn),加上其尺寸即為該CTRun的尺寸颖榜。
跟繞口令是的棚饵,不過就是這么個(gè)思路。
for (int i = 0; i < count; i ++) {//遍歷線的數(shù)組
CTLineRef line = (__bridge CTLineRef)arrLines[i];
NSArray * arrGlyphRun = (NSArray *)CTLineGetGlyphRuns(line);//獲取GlyphRun數(shù)組(GlyphRun:高效的字符繪制方案)
for (int j = 0; j < arrGlyphRun.count; j ++) {//遍歷CTRun數(shù)組
CTRunRef run = (__bridge CTRunRef)arrGlyphRun[j];//獲取CTRun
NSDictionary * attributes = (NSDictionary *)CTRunGetAttributes(run);//獲取CTRun的屬性
CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[attributes valueForKey:(id)kCTRunDelegateAttributeName];//獲取代理
if (delegate == nil) {//非空
continue;
}
NSDictionary * dic = CTRunDelegateGetRefCon(delegate);//判斷代理字典
if (![dic isKindOfClass:[NSDictionary class]]) {
continue;
}
CGPoint point = points[i];//獲取一個(gè)起點(diǎn)
CGFloat ascent;//獲取上距
CGFloat descent;//獲取下距
CGRect boundsRun;//創(chuàng)建一個(gè)frame
boundsRun.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
boundsRun.size.height = ascent + descent;//取得高
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);//獲取x偏移量
boundsRun.origin.x = point.x + xOffset;//point是行起點(diǎn)位置掩完,加上每個(gè)字的偏移量得到每個(gè)字的x
boundsRun.origin.y = point.y - descent;//計(jì)算原點(diǎn)
CGPathRef path = CTFrameGetPath(frame);//獲取繪制區(qū)域
CGRect colRect = CGPathGetBoundingBox(path);//獲取剪裁區(qū)域邊框
CGRect imageBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);
return imageBounds;
有了上面的思路這里就很好理解了噪漾。
外層for循環(huán)呢,是為了取到所有的CTLine
且蓬。
類型轉(zhuǎn)換什么的我就不多說了欣硼,然后通過CTLineGetGlyphRuns獲取一個(gè)CTLine中的所有CTRun
。
里層for循環(huán)是檢查每個(gè)CTRun恶阴。
通過CTRunGetAttributes拿到該CTRun的所有屬性
诈胜。
通過kvc取得屬性中的代理屬性
。
接下來判斷代理屬性是否為空
冯事。因?yàn)閳D片的占位符我們是綁定了代理的焦匈,而文字沒有。以此區(qū)分文字和圖片昵仅。
如果代理不為空缓熟,通過CTRunDelegateGetRefCon取得生成代理時(shí)綁定的對(duì)象
。判斷類型
是否是我們綁定的類型,防止取得我們之前為其他的富文本綁定過代理
够滑。
如果兩條都符合垦写,ok,這就是我們要的那個(gè)CTRun
版述。
開始計(jì)算該CTRun的frame
吧梯澜。
獲取原點(diǎn)
和獲取寬高
被。
通過CTRunGetTypographicBounds
取得寬渴析,ascent和descent晚伙。有了上面的介紹我們應(yīng)該知道圖片的高度就是ascent+descent了吧。
接下來獲取原點(diǎn)俭茧。
CTLineGetOffsetForStringIndex獲取對(duì)應(yīng)CTRun的X偏移量
咆疗。
取得對(duì)應(yīng)CTLine的原點(diǎn)的Y,減去圖片的下邊距才是圖片的原點(diǎn)
母债,這點(diǎn)應(yīng)該很好理解午磁。
至此,我們已經(jīng)獲得了圖片的frame
了毡们。因?yàn)橹唤壎艘粋€(gè)圖片迅皇,所以直接return就好了,如果多張圖片可以繼續(xù)遍歷返回?cái)?shù)組衙熔。
獲取到圖片的frame登颓,我們就可以繪制圖片了,用上面介紹的方法红氯。
哦框咙,別忘了手動(dòng)釋放你創(chuàng)建的對(duì)象哦。
CFRelease(frame);
CFRelease(path);
CFRelease(frameSetter);
大功告成痢甘。
好了喇嘱,至此你已經(jīng)完成圖片的繪制了。只要在ViewController里面引入你繪制CoreText文本的View正常的初始化添加子視圖就可以了塞栅。
好吧者铜,這個(gè)教程我也是綜合了很多資料寫出來的。優(yōu)勢(shì)是在于我一句一句講的放椰,幾乎每一句都告訴你原理了吧作烟。
恩,我也是在前人的基礎(chǔ)上自己總結(jié)查閱出來的庄敛,難免夾雜著個(gè)人理解和部分偏頗,如果各位看官發(fā)現(xiàn)我寫的有什么不對(duì)的地方歡迎與我聯(lián)系科汗,老司機(jī)的郵箱codewicky@163.com藻烤。
原諒老司機(jī)逗逼的本質(zhì),嚴(yán)肅不起來。
下面是一些參考資料:
coreText方法怖亭,列的很全
CTRun的詳細(xì)介紹
CTLine的詳細(xì)介紹
coreText基本原理及使用方法
圖文混排的整體介紹
coreText使用方法
你要是喜歡呢涎显,麻煩你動(dòng)一動(dòng)你可愛的小手點(diǎn)擊一下喜歡或者關(guān)注,畢竟老司機(jī)這么愛慕虛榮的人兴猩,而且老司機(jī)會(huì)經(jīng)常更新的期吓。
最后,你問我為什么一直叫自己老司機(jī)倾芝?哦讨勤,因?yàn)楹俸俸賬~~
哦,最后的最后晨另,若果真有人轉(zhuǎn)載的話潭千,麻煩你注明出處。
http://www.reibang.com/p/6db3289fb05d