一乓梨、前言
當(dāng)我們的產(chǎn)品某天站在你身后拍著你的肩膀說:"Hi guys,看看這個效果可以做不遣疯,對雄可,這段文字的首字大寫,文字主體呢用黑色缠犀,而文字中某些詞句可以顯示為紅色..."数苫,你可能會沉思一會,考慮是否用WebView+html或者直接制定一套規(guī)則然后用CoreGraphics去畫辨液,采用WebView+html的方式不失為一個辦法虐急,但是如果需求是TableView中每個Cell都要這么顯示,并且內(nèi)容不定室梅,cell高度可變戏仓,哈哈,光管理每個Cell中WebView的加載和高度計算就夠頭疼了亡鼠,還要考慮內(nèi)存問題赏殃,你懂的,WebView可是個吃內(nèi)存的大戶间涵;那么CoreGraphics+規(guī)則呢仁热?拜托,你是要山寨一個Word嗎勾哩?其實(shí)呢抗蠢,蘋果已經(jīng)為我們提供了一種富文本的解決方案:CoreText!
二思劳、一些概念
本著嚴(yán)謹(jǐn)?shù)闹螌W(xué)態(tài)度迅矛,先來了解一下一些基本概念,這些概念有助于讓我們知道屏幕上出現(xiàn)的文字是如何顯示潜叛,如果覺得只想知道CoreText是怎么用的秽褒,那么可以直接跳過這一章節(jié)壶硅。
我們平時接觸最多的排版系統(tǒng)應(yīng)該就是微軟的Word了吧,作為一名程序員销斟,我習(xí)慣在使用一些軟件的時候思考他們是怎么做的庐椒,對于Word這種軟件,如果我來開發(fā)蚂踊,簡單的方案就是字形+字號+前景色+背景色進(jìn)行圖像繪制(暫時不考慮下劃線约谈、刪除線和陰影等附加效果),比如一行文字犁钟,首先根據(jù)設(shè)置的字體獲得各個字的字形描述棱诱,字形描述可以是一個矩陣,矩陣中每個點(diǎn)有兩個值涝动,代表顯示前景色或背景色军俊,然后根據(jù)設(shè)置的字號按一定的算法縮放矩陣,最后根據(jù)前景色和背景色繪制出這個字的圖像捧存,當(dāng)這一行所有的字的圖像生成好后,那么這一行的高度應(yīng)該是這一行中高度最大的字圖像的高度担败,然后加上行間距昔穴。當(dāng)然現(xiàn)實(shí)情況是,Word比這個復(fù)雜的多的多提前,可以先看一下下面一些概念性的東西吗货,會更好的理解排版系統(tǒng)是怎么工作的(我覺得不管是Word還是CoreText,原理大同小異)狈网。
2.1 文字排版的概念
<B>字體(Font):</B>和我們平時說的字體不同宙搬,計算機(jī)意義上的字體表示的是同一大小,同一樣式(Style)字形的集合拓哺。從這個意義上來說勇垛,當(dāng)我們?yōu)槲淖衷O(shè)置粗體,斜體時其實(shí)是使用了另外一種字體(下劃線不算)士鸥。而平時我們所說的字體只是具有相同設(shè)計屬性的字體集合闲孤,即Font Family或typeface。
<B>字符(Character)和字形(Glyphs):</B>排版過程中一個重要的步驟就是從字符到字形的轉(zhuǎn)換烤礁,字符表示信息本身讼积,而字形是它的圖形表現(xiàn)形式。字符一般就是指某種編碼脚仔,如Unicode編碼勤众,而字形則是這些編碼對應(yīng)的圖片。但是他們之間不是一一對應(yīng)關(guān)系鲤脏,同個字符的不同字體族们颜,不同字體大小吕朵,不同字體樣式都對應(yīng)了不同的字形。而由于連寫(Ligatures)的存在掌桩,多個字符也會存在對應(yīng)一個字形的情況边锁。
</div>
- <B>字形描述集(Glyphs Metris):</B>即字形的各個參數(shù)。如下圖所示:
<div style='text-align:center'><img src="
http://7jpr6l.com1.z0.glb.clouddn.com/blog_img_coretext_glyphs1.png" alt="連字" /></div>
</div>
<B>邊框(Bounding Box):</B>一個假想的邊框波岛,盡可能地容納整個字形茅坛。
<B>基線(Baseline):</B>一條假想的參照線,以此為基礎(chǔ)進(jìn)行字形的渲染则拷。一般來說是一條橫線贡蓖。
<B>基礎(chǔ)原點(diǎn)(Origin):</B>基線上最左側(cè)的點(diǎn)。
<B>行間距(Leading):</B>行與行之間的間距煌茬。
<B>字間距(Kerning):</B>字與字之間的距離斥铺,為了排版的美觀,并不是所有的字形之間的距離都是一致的坛善,但是這個基本步影響到我們的文字排版晾蜘。
<B>上行高度(Ascent)和下行高度(Decent):</B>一個字形最高點(diǎn)和最低點(diǎn)到基線的距離,前者為正數(shù)眠屎,而后者為負(fù)數(shù)剔交。當(dāng)同一行內(nèi)有不同字體的文字時,就取最大值作為相應(yīng)的值改衩。這樣岖常,行高LineHeight = Ascent + |Descent| + Leading
2.2 CoreText
CoreText提供了一些低級的API用于富文本排版,它的數(shù)據(jù)源是NSAttributedString葫督。它可以根據(jù)NSAttributedString的定義的每個range的subNSAttributedString的樣式進(jìn)行對字符串的渲染竭鞍。可以這樣說橄镜,這是一個富文本渲染器偎快。
iOS/OSX中用于描述富文本的類是NSAttributedString,顧名思義洽胶,它比NSString多了Attribute的概念滨砍。它可以包含很多屬性,粗體妖异,斜體惋戏,下劃線,顏色他膳,背景色等等响逢,每個屬性都有其對應(yīng)的字符區(qū)域。在OSX上我們只需解析完畢相應(yīng)的數(shù)據(jù)棕孙,準(zhǔn)備好NSAttributedString即可舔亭,底層的繪制完全可以交給相應(yīng)的控件完成些膨。但是在iOS6.0之前就沒有這么方便,想要繪制Attributed String就需要用到CoreText了钦铺,iOS6.0以及以后的版本订雾,UILabel和UITextView提供了一個attributedText屬性,供我們設(shè)置AttributeString矛洞,然后UILabel和UITextView會調(diào)用CoreText進(jìn)行排版洼哎。
2.2.1 CoreText工作流
<div style='text-align:center'></div>
使用CoreText進(jìn)行NSAttributedString的繪制,最重要的兩個概念就是CTFrameSetter和CTFrame沼本。
其中CTFramesetter是由CFAttributedString(NSAttributedString)初始化而來噩峦,可以認(rèn)為它是CTFrame的一個Factory,通過傳入CGPath生成相應(yīng)的CTFrame并使用它進(jìn)行渲染:直接以CTFrame為參數(shù)使用CTFrameDraw繪制或者從CTFrame中獲取CTLine進(jìn)行微調(diào)后使用CTLineDraw進(jìn)行繪制抽兆。
一個CTFrame是由一行一行的CLine組成识补,每個CTLine又會包含若干個CTRun(既字形繪制的最小單元),通過相應(yīng)的方法可以獲取到不同位置的CTRun和CTLine辫红,以實(shí)現(xiàn)對不同位置touch事件的響應(yīng)凭涂。CTFrame可以認(rèn)為是一個整體的畫布(Canvas),
2.2.2 Attribute String
使用Attribute String主要有兩種途徑贴妻,一個是使用Foundation框架中的NSAttributeString或NSMutableAttributeString類导盅,另一個則是直接使用較為底層的API創(chuàng)建CFAttributedStringRef變量。我們可以猜測NSAttributeString和NSMutableAttributeString是對底層CFAttributedStringRef的封裝揍瑟。
使用NSAttributeString或NSMutableAttributeString相對簡單,如下:
NSString *string = @"這是一張小圖";
NSDictionary *attr = @{
NSFontAttributeName:[UIFont systemFontOfSize:14],
NSForegroundColorAttributeName:[UIColor redColor]
};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:attr];
我們可以看到初始化方法中有一個attributes參數(shù)乍炉,類型為NSDictionary绢片,也就是說在設(shè)置一段文字的排版時,Attribute主要是通過該參數(shù)以key-value的形式進(jìn)行設(shè)置岛琼。Foundation框架在iOS6.0之后提供NSString類型的key供我們使用底循,而且6.0之前,則需要使用CFStringRef類型的key槐瑞,我們需要對其進(jìn)行bridge轉(zhuǎn)換轉(zhuǎn)為NSString進(jìn)行使用熙涤。本文寫作時最高iOS版本為9.3,而且當(dāng)前絕大多數(shù)App已經(jīng)不支持iOS6以下甚至iOS7以下的設(shè)備困檩,所以我們著重看一下Foundation提供的NSString類型的Attribute Key祠挫。
NSString類型的Attribute Key有如下可選:
UIKIT_EXTERN NSString * const NSFontAttributeName NS_AVAILABLE(10_0, 6_0); // 字體,UIFont類型, 默認(rèn)為 Helvetica(Neue) 12
UIKIT_EXTERN NSString * const NSParagraphStyleAttributeName NS_AVAILABLE(10_0, 6_0); // 段落樣式,值為NSParagraphStyle類型, 默認(rèn)為defaultParagraphStyle
UIKIT_EXTERN NSString * const NSForegroundColorAttributeName NS_AVAILABLE(10_0, 6_0); // 前景色(文字顏色), UIColor類型悼沿, 默認(rèn)為黑色
UIKIT_EXTERN NSString * const NSBackgroundColorAttributeName NS_AVAILABLE(10_0, 6_0); // 背景色等舔, UIColor類型,默認(rèn)為nil
UIKIT_EXTERN NSString * const NSLigatureAttributeName NS_AVAILABLE(10_0, 6_0); // 連體屬性糟趾,取值為NSNumber 對象(整數(shù))慌植,0 表示沒有連體字符甚牲,1 表示使用默認(rèn)的連體字符
UIKIT_EXTERN NSString * const NSKernAttributeName NS_AVAILABLE(10_0, 6_0); // 字間距,float的NSNumber裝箱
UIKIT_EXTERN NSString * const NSStrikethroughStyleAttributeName NS_AVAILABLE(10_0, 6_0); // 設(shè)置刪除線蝶柿,取值為 NSNumber 對象(整數(shù))丈钙,枚舉常量
UIKIT_EXTERN NSString * const NSUnderlineStyleAttributeName NS_AVAILABLE(10_0, 6_0); // 設(shè)置下劃線,取值為 NSNumber 對象(整數(shù))交汤,0為沒有下劃線
UIKIT_EXTERN NSString * const NSStrokeColorAttributeName NS_AVAILABLE(10_0, 6_0); // 填充部分顏色雏赦,不是字體顏色,取值為 UIColor 對象
UIKIT_EXTERN NSString * const NSStrokeWidthAttributeName NS_AVAILABLE(10_0, 6_0); // 設(shè)置筆畫寬度蜻展,取值為 NSNumber 對象(整數(shù))喉誊,負(fù)值填充效果,正值中空效果
UIKIT_EXTERN NSString * const NSShadowAttributeName NS_AVAILABLE(10_0, 6_0); // 設(shè)置陰影屬性纵顾,取值為 NSShadow 對象
UIKIT_EXTERN NSString *const NSTextEffectAttributeName NS_AVAILABLE(10_10, 7_0); // 設(shè)置文本特殊效果伍茄,取值為 NSString 對象,(圖版印刷效果)
UIKIT_EXTERN NSString * const NSAttachmentAttributeName NS_AVAILABLE(10_0, 7_0); // 文本附件,取值為NSTextAttachment對象,常用于文字圖片混排
UIKIT_EXTERN NSString * const NSLinkAttributeName NS_AVAILABLE(10_0, 7_0); // 設(shè)置鏈接屬性施逾,點(diǎn)擊后調(diào)用瀏覽器打開指定URL地址
UIKIT_EXTERN NSString * const NSBaselineOffsetAttributeName NS_AVAILABLE(10_0, 7_0); // 設(shè)置基線偏移值敷矫,取值為 NSNumber (float),正值上偏,負(fù)值下偏
UIKIT_EXTERN NSString * const NSUnderlineColorAttributeName NS_AVAILABLE(10_0, 7_0); // 下劃線顏色
UIKIT_EXTERN NSString * const NSStrikethroughColorAttributeName NS_AVAILABLE(10_0, 7_0); // 刪除線顏色
UIKIT_EXTERN NSString * const NSObliquenessAttributeName NS_AVAILABLE(10_0, 7_0); // 字形傾斜度汉额,取值為 NSNumber (float),正值右傾曹仗,負(fù)值左傾
UIKIT_EXTERN NSString * const NSExpansionAttributeName NS_AVAILABLE(10_0, 7_0); // 文本橫向拉伸屬性,取值為 NSNumber (float),正值橫向拉伸文本蠕搜,負(fù)值橫向壓縮文本
UIKIT_EXTERN NSString * const NSWritingDirectionAttributeName NS_AVAILABLE(10_6, 7_0); // 文字書寫方向
UIKIT_EXTERN NSString * const NSVerticalGlyphFormAttributeName NS_AVAILABLE(10_7, 6_0); // 文字排版方向怎茫,取值為 NSNumber 對象(整數(shù)),0 表示橫排文本妓灌,1 表示豎排文本(目前iOS總是橫排文本)
三轨蛤、CoreText的使用
對于CoreText使用,對于我們程序員來說虫埂,應(yīng)該更喜歡簡單粗暴地直接上代碼祥山,如果把代碼分開按片段來講解,反而會使思維有斷層掉伏。
創(chuàng)建項目以及新建導(dǎo)航結(jié)構(gòu)不是本文重點(diǎn)缝呕,先略過了,針對CoreText的使用斧散,我簡單的從三個方面來演示:
- 簡單文本的文字排版供常,使用CoreText和CoteGraphics API
- 圖文混編加支持事件響應(yīng),使用CoreText和CoteGraphics API
- 圖文混編加支持事件響應(yīng)鸡捐,使用Foundation和UIKit框架提供的高級API
下面開始對上述內(nèi)容逐一進(jìn)行演示话侧,<font color="red"><b>注意:</b>前方代碼簡陋,沒有重用闯参,更無設(shè)計感瞻鹏,只是盡可能展示用法悲立,當(dāng)然用法也不可能在一篇文章中窮盡,請舉一反三新博!</font>
3.1 簡單文本的文字排版
新建一個類CTBaseView薪夕,內(nèi)容如下(有備注但不多,請自行腦補(bǔ)):
@implementation CTBaseView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = SHRGB(0xee, 0xee, 0xee);
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
// 1.垂直翻轉(zhuǎn)畫布
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
CGContextTranslateCTM(ctx, 0, self.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
// 2.創(chuàng)建path
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
// 3.創(chuàng)建Attribute String
NSMutableAttributedString *muAttrString = [[NSMutableAttributedString alloc]init];
// 3.1
NSString *string = @"H";
NSDictionary *attr = @{
NSFontAttributeName:[UIFont systemFontOfSize:20],
NSForegroundColorAttributeName:[UIColor redColor]
};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:attr];
[muAttrString appendAttributedString:attrString];
// 3.2
string = @"ello 復(fù)仇者聯(lián)盟赫悄!\n";
attr = @{
NSFontAttributeName:[UIFont systemFontOfSize:14],
NSForegroundColorAttributeName:[UIColor blueColor]
};
attrString = [[NSAttributedString alloc] initWithString:string attributes:attr];
[muAttrString appendAttributedString:attrString];
// 3.3
string = @"我們的主頁是:";
attrString = [[NSAttributedString alloc] initWithString:string];
[muAttrString appendAttributedString:attrString];
// 3.4
string = @"http://www.dianping.com\n";
attr = @{NSBackgroundColorAttributeName:[UIColor yellowColor],
NSForegroundColorAttributeName:SHRGB(0x72, 0xAC, 0xE3),
NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle),
NSLinkAttributeName:@"http://www.baidu.com"};
attrString = [[NSAttributedString alloc] initWithString:string attributes:attr];
[muAttrString appendAttributedString:attrString];
// 3.5
string = @"《復(fù)仇者聯(lián)盟》(Marvel's The Avengers)是漫威影業(yè)出品的一部科幻動作電影原献,取材自漫威漫畫,是漫威電影宇宙的第六部電影埂淮,同時也是第一階段的收官作品姑隅。由喬斯·韋登執(zhí)導(dǎo),小羅伯特·唐尼倔撞、克里斯·埃文斯讲仰、克里斯·海姆斯沃斯、馬克·魯法洛痪蝇、斯嘉麗·約翰遜鄙陡、杰瑞米·雷納和湯姆·希德勒斯頓聯(lián)袂出演。\n影片講述了神盾局指揮官尼克·弗瑞為了對付《雷神》中被流放的洛基躏啰,積極奔走尋找最強(qiáng)者趁矾,在神盾局斡旋下將鋼鐵俠、美國隊長给僵、雷神托爾毫捣、綠巨人、黑寡婦和鷹眼俠六位超級英雄集結(jié)在一起帝际,組成了復(fù)仇者聯(lián)盟蔓同,共同攜手應(yīng)對邪神洛基。\n影片于2012年5月5日在中國內(nèi)地正式上映胡本。";
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc]init];
style.lineSpacing = 5;
style.paragraphSpacing = 20;
attr = @{
NSFontAttributeName:[UIFont systemFontOfSize:14],
NSForegroundColorAttributeName:[UIColor orangeColor],
NSParagraphStyleAttributeName:style
};
attrString = [[NSAttributedString alloc] initWithString:string attributes:attr];
[muAttrString appendAttributedString:attrString];
// 4.創(chuàng)建Framesetter和Frame
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)muAttrString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, muAttrString.length), path, NULL);
// 5.打印Frame中l(wèi)ine和run信息
NSArray *lines = (NSArray*)CTFrameGetLines(frame);
NSInteger lineCount = lines.count;
CGPoint lineOrigin[lineCount];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), lineOrigin);
for (NSInteger i=0; i<lineCount; i++) {
CTLineRef line = (__bridge CTLineRef)lines[i];
NSArray *runs = (NSArray*)CTLineGetGlyphRuns(line);
NSInteger runCount = runs.count;
for (NSInteger j=0; j<runCount; j++) {
CTRunRef run = (__bridge CTRunRef)runs[j];
CFRange range = CTRunGetStringRange(run);
NSLog(@"==> line:%@ - run:%@ [%@ , %@]", @(i),@(j),@(range.location),@(range.length));
}
}
CTFrameDraw(frame, ctx);
CFRelease(frame);
CFRelease(framesetter);
CFRelease(path);
CGContextRestoreGState(ctx);
}
@end
展示效果如下:
<div style='text-align:center'></div>
3.2 圖文混編加支持事件響應(yīng)
新建一個類CTMixView,內(nèi)容如下(有備注但不多畸悬,請自行腦補(bǔ)):
static CGFloat ascentCallback(void *ref)
{
return [[((__bridge NSDictionary*)ref) objectForKey:@"height"] floatValue];
}
static CGFloat descentCallback(void *ref)
{
return 0;
}
static CGFloat widthCallback(void *ref)
{
return [[((__bridge NSDictionary*)ref) objectForKey:@"width"] floatValue];
}
@interface CTMixView ()
@property (nonatomic, strong) NSMutableArray *imageRects;
@end
@implementation CTMixView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = SHRGB(0xee, 0xee, 0xee);
self.imageRects = [NSMutableArray new];
UIGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
[self addGestureRecognizer:gesture];
self.userInteractionEnabled = YES;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
// 垂直翻轉(zhuǎn)畫布
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
CGContextTranslateCTM(ctx, 0, self.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
// 2.創(chuàng)建path
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
// 3.創(chuàng)建Attribute String
NSMutableAttributedString *muAttrString = [[NSMutableAttributedString alloc]init];
// 3.1
NSString *string = @"這是一張小圖";
NSDictionary *attr = @{
NSFontAttributeName:[UIFont systemFontOfSize:14],
NSForegroundColorAttributeName:[UIColor redColor]
};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:attr];
[muAttrString appendAttributedString:attrString];
// 3.2 添加一張小圖
[self addImageWithWidth:50 height:50 toAttrString:muAttrString];
// 3.3
string = @"侧甫,這是一張大圖";
attr = @{
NSFontAttributeName:[UIFont systemFontOfSize:14],
NSForegroundColorAttributeName:[UIColor redColor]
};
attrString = [[NSAttributedString alloc] initWithString:string attributes:attr];
[muAttrString appendAttributedString:attrString];
// 3.4 添加一張大圖
[self addImageWithWidth:200 height:200 toAttrString:muAttrString];
// 3.5
string = @"后面沒圖了";
attr = @{
NSFontAttributeName:[UIFont systemFontOfSize:20],
NSForegroundColorAttributeName:[UIColor blueColor]
};
attrString = [[NSAttributedString alloc] initWithString:string attributes:attr];
[muAttrString appendAttributedString:attrString];
// 4.創(chuàng)建Framesetter和Frame
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)muAttrString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, muAttrString.length), path, NULL);
// 5.獲取添加的圖片的Rect
NSArray *lines = (NSArray*)CTFrameGetLines(frame);
NSInteger lineCount = lines.count;
CGPoint lineOrigin[lineCount];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), lineOrigin);
for (NSInteger i=0; i<lineCount; i++) {
CTLineRef line = (__bridge CTLineRef)lines[i];
NSArray *runs = (NSArray*)CTLineGetGlyphRuns(line);
NSInteger runCount = runs.count;
for (NSInteger j=0; j<runCount; j++) {
CTRunRef run = (__bridge CTRunRef)runs[j];
NSDictionary *dicAttr = (NSDictionary*)CTRunGetAttributes(run);
CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[dicAttr objectForKey:(id)kCTRunDelegateAttributeName];
if (delegate == nil) {
continue;
}
NSDictionary *metaData = CTRunDelegateGetRefCon(delegate);
if (metaData == nil) {
continue;
}
CGRect rect;
CGFloat ascent;
CGFloat descent;
rect.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
rect.size.height = ascent + descent;
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
rect.origin.x = lineOrigin[i].x + xOffset;
rect.origin.y = lineOrigin[i].y - descent;
CGPathRef tempPath = CTFrameGetPath(frame);
CGRect colRect = CGPathGetBoundingBox(tempPath);
rect = CGRectOffset(rect, colRect.origin.x, colRect.origin.y);
NSValue *value = [NSValue valueWithCGRect:rect];
[self.imageRects addObject:value];
}
}
// 6.繪制文本,釋放內(nèi)存
CTFrameDraw(frame, ctx);
CFRelease(frame);
CFRelease(framesetter);
CFRelease(path);
// 7.繪制圖片
// 7.1 繪制小圖
UIImage *image = [UIImage imageNamed:@"50x50"];
NSValue *value = self.imageRects[0];
CGRect imgRect = [value CGRectValue];
CGContextDrawImage(ctx, imgRect, image.CGImage);
// 7.2 繪制大圖
image = [UIImage imageNamed:@"200x200"];
value = self.imageRects[1];
imgRect = [value CGRectValue];
CGContextDrawImage(ctx, imgRect, image.CGImage);
// 恢復(fù)context
CGContextRestoreGState(ctx);
}
- (void)addImageWithWidth:(CGFloat)width height:(CGFloat)height toAttrString:(NSMutableAttributedString*)string
{
CTRunDelegateCallbacks callbacks;
memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks));
callbacks.version = kCTRunDelegateVersion1;
callbacks.getAscent = ascentCallback;
callbacks.getWidth = widthCallback;
callbacks.getDescent = descentCallback;
CTRunDelegateRef runDelegate = CTRunDelegateCreate(&callbacks, (__bridge void*)@{@"width":@(width),@"height":@(height)});
unichar placeHolder = 0xFFFC;
NSString *strPlaceHolder = [[NSString alloc] initWithCharacters:&placeHolder length:1];
NSMutableAttributedString *placeHolerAttr = [[NSMutableAttributedString alloc] initWithString:strPlaceHolder];
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolerAttr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, runDelegate);
CFRelease(runDelegate);
[string appendAttributedString:placeHolerAttr];
}
- (void)onTap:(UIGestureRecognizer*)gesture
{
CGPoint point = [gesture locationInView:self];
[self.imageRects enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger idx, BOOL * _Nonnull stop) {
CGRect rect = value.CGRectValue;
CGFloat y = self.height - rect.origin.y - rect.size.height;
CGRect imgRect = CGRectMake(rect.origin.x, y, rect.size.width, rect.size.height);
if (CGRectContainsPoint(imgRect, point)) {
NSString *text = idx == 0 ? @"點(diǎn)擊了小圖" : @"點(diǎn)擊了大圖";
UIAlertView *av = [[UIAlertView alloc]initWithTitle:nil message:text delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil];
[av show];
*stop = YES;
}
}];
}
@end
展示效果如下:
<div style='text-align:center'></div>
3.3 圖文混編加支持事件響應(yīng)(高級接口)
新建一個類CTMixView蹋宦,內(nèi)容如下(有備注但不多披粟,請自行腦補(bǔ)):
@interface CTHightLevelMixView ()
@property (nonatomic, strong) NSMutableArray *positons;
@end
@implementation CTHightLevelMixView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.positons = [NSMutableArray new];
self.backgroundColor = SHRGB(0xee, 0xee, 0xee);
[self displayContent];
UIGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
[self addGestureRecognizer:gesture];
self.userInteractionEnabled = YES;
}
return self;
}
- (void)displayContent
{
NSMutableAttributedString *muAttrString = [[NSMutableAttributedString alloc]init];
NSString *string = @"這是一張小圖";
NSDictionary *attr = @{
NSFontAttributeName:[UIFont systemFontOfSize:14],
NSForegroundColorAttributeName:[UIColor redColor]
};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:attr];
[muAttrString appendAttributedString:attrString];
// 3.2 添加一張小圖
[self addImage:@"50x50" width:50 height:50 toAttrString:muAttrString];
// 3.3
string = @",這是一張大圖";
attr = @{
NSFontAttributeName:[UIFont systemFontOfSize:14],
NSForegroundColorAttributeName:[UIColor redColor]
};
attrString = [[NSAttributedString alloc] initWithString:string attributes:attr];
[muAttrString appendAttributedString:attrString];
// 3.4 添加一張大圖
[self addImage:@"200x200" width:200 height:200 toAttrString:muAttrString];
// 3.5
string = @"后面沒圖了";
attr = @{
NSFontAttributeName:[UIFont systemFontOfSize:20],
NSForegroundColorAttributeName:[UIColor blueColor]
};
attrString = [[NSAttributedString alloc] initWithString:string attributes:attr];
[muAttrString appendAttributedString:attrString];
self.attributedText = muAttrString;
}
- (void)addImage:(NSString*)imageName width:(CGFloat)width height:(CGFloat)height toAttrString:(NSMutableAttributedString*)attrString
{
NSTextAttachment *imageAttachment = [[NSTextAttachment alloc]init];
imageAttachment.image = [UIImage imageNamed:imageName];
imageAttachment.bounds = CGRectMake(0, 0, width, height);
NSAttributedString *string = [NSAttributedString attributedStringWithAttachment:imageAttachment];
NSRange range = NSMakeRange(attrString.length, string.length);
NSValue *rangeValue = [NSValue valueWithRange:range];
[self.positons addObject:rangeValue];
[attrString appendAttributedString:string];
}
- (void)onTap:(UIGestureRecognizer*)gesture
{
CGPoint point = [gesture locationInView:self];
[self.positons enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger idx, BOOL * _Nonnull stop) {
NSRange range = [value rangeValue];
self.selectedRange = range;
NSArray *rects = [self selectionRectsForRange:self.selectedTextRange];
self.selectedRange = NSMakeRange(0, 0);
for (UITextSelectionRect *textRect in rects) {
CGRect rect = textRect.rect;
if (CGRectContainsPoint(rect, point)) {
NSString *text = idx == 0 ? @"點(diǎn)擊了小圖" : @"點(diǎn)擊了大圖";
UIAlertView *av = [[UIAlertView alloc]initWithTitle:nil message:text delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil];
[av show];
*stop = YES;
}
}
}];
}
@end
展示效果如下:
<div style='text-align:center'></div>
本文Demo下載:Demo源碼