一、前言
当我们的产品某天站在你身后拍着你的肩膀说:"Hi guys,看看这个效果可以做不,对,这段文字的首字大写,文字主体呢用黑色,而文字中某些词句可以显示为红色...",你可能会沉思一会,考虑是否用WebView+html或者直接制定一套规则然后用CoreGraphics去画,采用WebView+html的方式不失为一个办法,但是如果需求是TableView中每个Cell都要这么显示,并且内容不定,cell高度可变,哈哈,光管理每个Cell中WebView的加载和高度计算就够头疼了,还要考虑内存问题,你懂的,WebView可是个吃内存的大户;那么CoreGraphics+规则呢?拜托,你是要山寨一个Word吗?其实呢,苹果已经为我们提供了一种富文本的解决方案:CoreText!
二、一些概念
本着严谨的治学态度,先来了解一下一些基本概念,这些概念有助于让我们知道屏幕上出现的文字是如何显示,如果觉得只想知道CoreText是怎么用的,那么可以直接跳过这一章节。
我们平时接触最多的排版系统应该就是微软的Word了吧,作为一名程序员,我习惯在使用一些软件的时候思考他们是怎么做的,对于Word这种软件,如果我来开发,简单的方案就是字形+字号+前景色+背景色进行图像绘制(暂时不考虑下划线、删除线和阴影等附加效果),比如一行文字,首先根据设置的字体获得各个字的字形描述,字形描述可以是一个矩阵,矩阵中每个点有两个值,代表显示前景色或背景色,然后根据设置的字号按一定的算法缩放矩阵,最后根据前景色和背景色绘制出这个字的图像,当这一行所有的字的图像生成好后,那么这一行的高度应该是这一行中高度最大的字图像的高度,然后加上行间距。当然现实情况是,Word比这个复杂的多的多,可以先看一下下面一些概念性的东西,会更好的理解排版系统是怎么工作的(我觉得不管是Word还是CoreText,原理大同小异)。
2.1 文字排版的概念
<B>字体(Font):</B>和我们平时说的字体不同,计算机意义上的字体表示的是同一大小,同一样式(Style)字形的集合。从这个意义上来说,当我们为文字设置粗体,斜体时其实是使用了另外一种字体(下划线不算)。而平时我们所说的字体只是具有相同设计属性的字体集合,即Font Family或typeface。
<B>字符(Character)和字形(Glyphs):</B>排版过程中一个重要的步骤就是从字符到字形的转换,字符表示信息本身,而字形是它的图形表现形式。字符一般就是指某种编码,如Unicode编码,而字形则是这些编码对应的图片。但是他们之间不是一一对应关系,同个字符的不同字体族,不同字体大小,不同字体样式都对应了不同的字形。而由于连写(Ligatures)的存在,多个字符也会存在对应一个字形的情况。
</div>
- <B>字形描述集(Glyphs Metris):</B>即字形的各个参数。如下图所示:
<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>一条假想的参照线,以此为基础进行字形的渲染。一般来说是一条横线。
<B>基础原点(Origin):</B>基线上最左侧的点。
<B>行间距(Leading):</B>行与行之间的间距。
<B>字间距(Kerning):</B>字与字之间的距离,为了排版的美观,并不是所有的字形之间的距离都是一致的,但是这个基本步影响到我们的文字排版。
<B>上行高度(Ascent)和下行高度(Decent):</B>一个字形最高点和最低点到基线的距离,前者为正数,而后者为负数。当同一行内有不同字体的文字时,就取最大值作为相应的值。这样,行高LineHeight = Ascent + |Descent| + Leading
2.2 CoreText
CoreText提供了一些低级的API用于富文本排版,它的数据源是NSAttributedString。它可以根据NSAttributedString的定义的每个range的subNSAttributedString的样式进行对字符串的渲染。可以这样说,这是一个富文本渲染器。
iOS/OSX中用于描述富文本的类是NSAttributedString,顾名思义,它比NSString多了Attribute的概念。它可以包含很多属性,粗体,斜体,下划线,颜色,背景色等等,每个属性都有其对应的字符区域。在OSX上我们只需解析完毕相应的数据,准备好NSAttributedString即可,底层的绘制完全可以交给相应的控件完成。但是在iOS6.0之前就没有这么方便,想要绘制Attributed String就需要用到CoreText了,iOS6.0以及以后的版本,UILabel和UITextView提供了一个attributedText属性,供我们设置AttributeString,然后UILabel和UITextView会调用CoreText进行排版。
2.2.1 CoreText工作流
<div style='text-align:center'></div>
使用CoreText进行NSAttributedString的绘制,最重要的两个概念就是CTFrameSetter和CTFrame。
其中CTFramesetter是由CFAttributedString(NSAttributedString)初始化而来,可以认为它是CTFrame的一个Factory,通过传入CGPath生成相应的CTFrame并使用它进行渲染:直接以CTFrame为参数使用CTFrameDraw绘制或者从CTFrame中获取CTLine进行微调后使用CTLineDraw进行绘制。
一个CTFrame是由一行一行的CLine组成,每个CTLine又会包含若干个CTRun(既字形绘制的最小单元),通过相应的方法可以获取到不同位置的CTRun和CTLine,以实现对不同位置touch事件的响应。CTFrame可以认为是一个整体的画布(Canvas),
2.2.2 Attribute String
使用Attribute String主要有两种途径,一个是使用Foundation框架中的NSAttributeString或NSMutableAttributeString类,另一个则是直接使用较为底层的API创建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参数,类型为NSDictionary,也就是说在设置一段文字的排版时,Attribute主要是通过该参数以key-value的形式进行设置。Foundation框架在iOS6.0之后提供NSString类型的key供我们使用,而且6.0之前,则需要使用CFStringRef类型的key,我们需要对其进行bridge转换转为NSString进行使用。本文写作时最高iOS版本为9.3,而且当前绝大多数App已经不支持iOS6以下甚至iOS7以下的设备,所以我们着重看一下Foundation提供的NSString类型的Attribute Key。
NSString类型的Attribute Key有如下可选:
UIKIT_EXTERN NSString * const NSFontAttributeName NS_AVAILABLE(10_0, 6_0); // 字体,UIFont类型, 默认为 Helvetica(Neue) 12
UIKIT_EXTERN NSString * const NSParagraphStyleAttributeName NS_AVAILABLE(10_0, 6_0); // 段落样式,值为NSParagraphStyle类型, 默认为defaultParagraphStyle
UIKIT_EXTERN NSString * const NSForegroundColorAttributeName NS_AVAILABLE(10_0, 6_0); // 前景色(文字颜色), UIColor类型, 默认为黑色
UIKIT_EXTERN NSString * const NSBackgroundColorAttributeName NS_AVAILABLE(10_0, 6_0); // 背景色, UIColor类型,默认为nil
UIKIT_EXTERN NSString * const NSLigatureAttributeName NS_AVAILABLE(10_0, 6_0); // 连体属性,取值为NSNumber 对象(整数),0 表示没有连体字符,1 表示使用默认的连体字符
UIKIT_EXTERN NSString * const NSKernAttributeName NS_AVAILABLE(10_0, 6_0); // 字间距,float的NSNumber装箱
UIKIT_EXTERN NSString * const NSStrikethroughStyleAttributeName NS_AVAILABLE(10_0, 6_0); // 设置删除线,取值为 NSNumber 对象(整数),枚举常量
UIKIT_EXTERN NSString * const NSUnderlineStyleAttributeName NS_AVAILABLE(10_0, 6_0); // 设置下划线,取值为 NSNumber 对象(整数),0为没有下划线
UIKIT_EXTERN NSString * const NSStrokeColorAttributeName NS_AVAILABLE(10_0, 6_0); // 填充部分颜色,不是字体颜色,取值为 UIColor 对象
UIKIT_EXTERN NSString * const NSStrokeWidthAttributeName NS_AVAILABLE(10_0, 6_0); // 设置笔画宽度,取值为 NSNumber 对象(整数),负值填充效果,正值中空效果
UIKIT_EXTERN NSString * const NSShadowAttributeName NS_AVAILABLE(10_0, 6_0); // 设置阴影属性,取值为 NSShadow 对象
UIKIT_EXTERN NSString *const NSTextEffectAttributeName NS_AVAILABLE(10_10, 7_0); // 设置文本特殊效果,取值为 NSString 对象,(图版印刷效果)
UIKIT_EXTERN NSString * const NSAttachmentAttributeName NS_AVAILABLE(10_0, 7_0); // 文本附件,取值为NSTextAttachment对象,常用于文字图片混排
UIKIT_EXTERN NSString * const NSLinkAttributeName NS_AVAILABLE(10_0, 7_0); // 设置链接属性,点击后调用浏览器打开指定URL地址
UIKIT_EXTERN NSString * const NSBaselineOffsetAttributeName NS_AVAILABLE(10_0, 7_0); // 设置基线偏移值,取值为 NSNumber (float),正值上偏,负值下偏
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),正值右倾,负值左倾
UIKIT_EXTERN NSString * const NSExpansionAttributeName NS_AVAILABLE(10_0, 7_0); // 文本横向拉伸属性,取值为 NSNumber (float),正值横向拉伸文本,负值横向压缩文本
UIKIT_EXTERN NSString * const NSWritingDirectionAttributeName NS_AVAILABLE(10_6, 7_0); // 文字书写方向
UIKIT_EXTERN NSString * const NSVerticalGlyphFormAttributeName NS_AVAILABLE(10_7, 6_0); // 文字排版方向,取值为 NSNumber 对象(整数),0 表示横排文本,1 表示竖排文本(目前iOS总是横排文本)
三、CoreText的使用
对于CoreText使用,对于我们程序员来说,应该更喜欢简单粗暴地直接上代码,如果把代码分开按片段来讲解,反而会使思维有断层。
创建项目以及新建导航结构不是本文重点,先略过了,针对CoreText的使用,我简单的从三个方面来演示:
- 简单文本的文字排版,使用CoreText和CoteGraphics API
- 图文混编加支持事件响应,使用CoreText和CoteGraphics API
- 图文混编加支持事件响应,使用Foundation和UIKit框架提供的高级API
下面开始对上述内容逐一进行演示,<font color="red"><b>注意:</b>前方代码简陋,没有重用,更无设计感,只是尽可能展示用法,当然用法也不可能在一篇文章中穷尽,请举一反三!</font>
3.1 简单文本的文字排版
新建一个类CTBaseView,内容如下(有备注但不多,请自行脑补):
@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.垂直翻转画布
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
CGContextTranslateCTM(ctx, 0, self.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
// 2.创建path
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
// 3.创建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 复仇者联盟!\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 = @"《复仇者联盟》(Marvel's The Avengers)是漫威影业出品的一部科幻动作电影,取材自漫威漫画,是漫威电影宇宙的第六部电影,同时也是第一阶段的收官作品。由乔斯·韦登执导,小罗伯特·唐尼、克里斯·埃文斯、克里斯·海姆斯沃斯、马克·鲁法洛、斯嘉丽·约翰逊、杰瑞米·雷纳和汤姆·希德勒斯顿联袂出演。\n影片讲述了神盾局指挥官尼克·弗瑞为了对付《雷神》中被流放的洛基,积极奔走寻找最强者,在神盾局斡旋下将钢铁侠、美国队长、雷神托尔、绿巨人、黑寡妇和鹰眼侠六位超级英雄集结在一起,组成了复仇者联盟,共同携手应对邪神洛基。\n影片于2012年5月5日在中国内地正式上映。";
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.创建Framesetter和Frame
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)muAttrString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, muAttrString.length), path, NULL);
// 5.打印Frame中line和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 图文混编加支持事件响应
新建一个类CTMixView,内容如下(有备注但不多,请自行脑补):
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);
// 垂直翻转画布
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
CGContextTranslateCTM(ctx, 0, self.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
// 2.创建path
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
// 3.创建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.创建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.绘制文本,释放内存
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);
// 恢复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 ? @"点击了小图" : @"点击了大图";
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 图文混编加支持事件响应(高级接口)
新建一个类CTMixView,内容如下(有备注但不多,请自行脑补):
@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 ? @"点击了小图" : @"点击了大图";
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源码