IOS CoreText.framework — 基本用法
http://blog.youkuaiyun.com/fengsh998/article/details/8691823
IOS CoreText.framework — 段落样子CTParagraphStyle
http://blog.youkuaiyun.com/fengsh998/article/details/8700627
IOS CoreText.framework — 行 CTLineRef
http://blog.youkuaiyun.com/fengsh998/article/details/8701738
IOS CoreText.framework — 图文混排
http://blog.youkuaiyun.com/fengsh998/article/details/8714497
学习完了上述四篇博文准备做一个使用CoreText实现的图文混排的案例
由于CoreText采用的是底层的绘制方法,所以案例的实现要放在draw方法中进行实现
创建一个自定义视图CommonDetailView继承自UIView
在CommonDetailView.m文件中重写- (void)drawRect:(CGRect)rect;
- (void)drawRect:(CGRect)rect的调用只有在使用到此类的实例时才会调用
比如
CommonDetailView *cv =[[CommonDetailView alloc] init];
//此时drawRect并不会调用
只有当
[self.view addSubview:cv];
//此时调用CommonDetailView的drawRect方法
这其实是视图的加载时的一种懒加载方式
下面开始重写drawRect方法
- (void)drawRect:(CGRect)rect {
//得到绘图上下文对象(上下文是一种属性的有序序列,它们为驻留在环境内的对象定义环境。在对象的激活过程中创建上下文,对象被配置为要求某些自动服务,如同步、事务、实时激活、安全性等等)
CGContextRef context = UIGraphicsGetCurrentContext();
//设置字形变换矩阵为CGAffineTransformIdentity,也就是说每一个字形都不做图形变换
// CGContextSetTextMatrix(context, CGAffineTransformIdentity);
//CGAffineTransformMake(CGFloat a, CGFloat b, CGFloat c, CGFloat d,CGFloat tx, CGFloat ty)
//ad缩放,bc旋转,tx,ty位移
CGAffineTransform flipVertical = CGAffineTransformMake(1,0,0,-1,0,self.bounds.size.height);
//将当前context的坐标系进行翻转(如果没有将坐标系进行翻转,则绘制结果会倒置)
CGContextConcatCTM(context, flipVertical);
//创建需要绘制的文本
//标题
NSString *str = @"第一段:由触控科技主办的《Cocos 2015开发者大会(春季)》将于4月2日正式召开。作为当前市场占有率最高的开源手游引擎,今年的cocos开发者大会吸引了大批媒体和从业者的关注。门票一经发售,即引发了抢购热潮。第二段:为了满足不同参会者的需求,此次官方贴心地设置了多种梯队门票,包括免费票、个人票、团体票及VIP众筹门票。据悉,目前距离大会仍有约两周的时间,门票销量已经超过一半,还没有购票的小伙伴们要抓紧时间啦!";
//创建属性字符串
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]initWithString:str];
//对此段文本使用的字体属性
UIFont *font = [UIFont fontWithName:nil size:kTitleFontSize];
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)nil, font.pointSize, nil);
//为属性文本添加字体属性
[attributedString addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)fontRef range:NSMakeRange(0, str.length)];
CFRelease(fontRef);
//为图片设置CTRunDelegate,delegate决定留给图片的空间大小等信息
//所有的代理方法不能使用外部成员变量,只能使用本身的参数
CTRunDelegateCallbacks imageCallbacks;
imageCallbacks.version = kCTRunDelegateCurrentVersion;
imageCallbacks.dealloc = RunDelegateDeallocCallback;
imageCallbacks.getAscent = RunDelegateGetAscentCallback;
imageCallbacks.getDescent = RunDelegateGetDescentCallback;
imageCallbacks.getWidth = RunDelegateGetWidthCallback;
//创建CTRunDelegateRef
UIImage *image = [UIImage imageWithData:[[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:@"https://ss0.bdstatic.com/5a21bjqh_Q23odCf/static/superplus/img/logo_white_ee663702.png"]]];
CTRunDelegateRef runDelegate = CTRunDelegateCreate(&imageCallbacks, (__bridge void *)(image));
//创建需要绘制的图片的占位文本字符串
NSMutableAttributedString *imageAttributedString = [[NSMutableAttributedString alloc] initWithString:@" "];//空格用于给图片留位置
//为占位文本添加代理属性(即决定要绘制时的属性:如图片的宽高等),绘制时便会自动调用代理方法,为图片留出空间
//NSMakeRange(0, 1)表示从文本的0位置往后1个字符将会用图片进行替换
[imageAttributedString addAttribute:(NSString *)kCTRunDelegateAttributeName value:(__bridge id)runDelegate range:NSMakeRange(0, 1)];
//释放CTRunDelegateRef
// CFRelease(runDelegate);
//为占位文本添加属性(即决定要绘制时的属性)
//这里添加的属性是要绘制的图片
NSString *imgName = [NSString stringWithFormat:@"https://ss0.bdstatic.com/5a21bjqh_Q23odCf/static/superplus/img/logo_white_ee663702.png"];
//在属性字符串0-1的位置添加属性,值为图片的地址
[imageAttributedString addAttribute:@"imageName" value:imgName range:NSMakeRange(0, 1)];
//将占位文本插入到全局要绘制的属性字符串的某个位置处(这里就添加到第二段文字前)
NSRange range = [str rangeOfString:@"第二段"];
[attributedString insertAttributedString:imageAttributedString atIndex:NSMakeRange(0, rang.location)];
//设置段落格式
//换行模式
CTLineBreakMode lineBreak = kCTLineBreakByCharWrapping;
CTParagraphStyleSetting lineBreakMode;
lineBreakMode.spec = kCTParagraphStyleSpecifierLineBreakMode;
lineBreakMode.value = &lineBreak;
lineBreakMode.valueSize = sizeof(CTLineBreakMode);
//首行缩进若干坐标点
CGFloat fristlineindent = 10.0f;
CTParagraphStyleSetting fristline;
fristline.spec = kCTParagraphStyleSpecifierFirstLineHeadIndent;
fristline.value = &fristlineindent;
fristline.valueSize = sizeof(float);
//最大行高
CGFloat _linespace = 5.0f;
CTParagraphStyleSetting lineSpaceSetting;
lineSpaceSetting.spec = kCTParagraphStyleSpecifierLineSpacing;
lineSpaceSetting.value = &_linespace;
lineSpaceSetting.valueSize = sizeof(float);
CTParagraphStyleSetting settings[] = {
lineBreakMode,
fristline,
lineSpaceSetting,
};
//段落格式属性组
CTParagraphStyleRef style = CTParagraphStyleCreate(settings, 3);
//将段落格式属性组转成字典
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithObject:(__bridge id)style forKey:(id)kCTParagraphStyleAttributeName ];
CFRelease(style);
//全局绘制信息添加段落格式属性组
[attributedString addAttributes:attributes range:NSMakeRange(0, [attributedString length])];
//CTFramesetter是CTFrame对象工厂,而一个CTFrame就是一个段落,一个段落包含多个行CTLine,一行包括多个分割CTRun
CTFramesetterRef ctFramesetter = CTFramesetterCreateWithAttributedString((CFMutableAttributedStringRef)attributedString);
//创建图形绘制句柄(即绘制路径)
CGMutablePathRef path = CGPathCreateMutable();
//绘制区域
CGRect bounds = CGRectMake(0, -10, 320, 500);
//添加绘制区域到绘制句柄(之后通过上下文来进行绘制)
CGPathAddRect(path, NULL, bounds);
//从CTFramesetter工厂中生产CTFrame对象(分割段落)CFRangeMake表示范围
CTFrameRef ctFrame = CTFramesetterCreateFrame(ctFramesetter,CFRangeMake(0,str.length), path, NULL);
//开始绘制(会回调代理方法,为图片留出区域)
CTFrameDraw(ctFrame, context);
//绘制图片=======================================================================================
//得到所有绘制行
CFArrayRef lines = CTFrameGetLines(ctFrame);
MyLog(@"总行数%ld",CFArrayGetCount(lines));
//CFArrayGetCount(lines):得到绘制的行数
CGPoint lineOrigins[CFArrayGetCount(lines)];
//得到行原点 也就是ctFrame从哪开始,
CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), lineOrigins);
//循环遍历所有绘制行
for (int i = 0; i < CFArrayGetCount(lines); i++) {
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGFloat lineAscent;//原点上行线高度
CGFloat lineDescent;//原点下行线高度
CGFloat lineLeading;//行距
//CTLineGetTypographicBounds:计算排版的属性赋值
CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
//PS:此时行的高度 = lineAscent + lineDescent + lineLeading;
// MyLog(@"行=%d,lineAscent=%f,lineDescent=%f",i,lineAscent,lineDescent);
//获取每一行的CTRun数组(CTRun的分割规则是,根据标点符号或者图片进行分割)
CFArrayRef runs = CTLineGetGlyphRuns(line);
for (int j = 0; j < CFArrayGetCount(runs); j++) {
CGFloat runAscent;
CGFloat runDescent;
//获取CTRun原点
CGPoint lineOrigin = lineOrigins[i];
//获取CTRun
CTRunRef run = CFArrayGetValueAtIndex(runs, j);
//得到给CTRun设置的属性
NSDictionary* attributes = (NSDictionary *)CTRunGetAttributes(run);
//此处runRect保存的是图片所在行的rect
CGRect runRect;
//CTLineGetTypographicBounds计算排版的属性赋值
runRect.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0,0), &runAscent, &runDescent, NULL);
runRect=CGRectMake(lineOrigin.x + CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL), lineOrigin.y - runDescent, runRect.size.width, runAscent + runDescent);
//从属性字典中取出图片地址
NSString *imageName = [attributes objectForKey:@"imageName"];
//图片渲染逻辑
if (imageName) {
// UIImage *image = [UIImage imageNamed:imageName];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@",imageName]];
//下载图片
UIImage *image = [UIImage imageWithData:[[NSData alloc] initWithContentsOfURL:url]];
if (image) {
//设置绘制的图片区域
CGRect imageDrawRect;
imageDrawRect.size.width = 320 * 0.2;
imageDrawRect.size.height = 320 * 0.2;
// imageDrawRect.origin.x = runRect.origin.x + lineOrigin.x - 24;
imageDrawRect.origin.x = (320 - 320 * 0.2) / 2;
imageDrawRect.origin.y = runRect.origin.y;
//绘制图片
CGContextDrawImage(context, imageDrawRect, image.CGImage);
}
}
}
}
CFRelease(ctFramesetter);
CFRelease(path);
CFRelease(ctFrame);
}
#pragma mark -为要绘制的图片留出位置
void RunDelegateDeallocCallback( void* refCon ){
}
//控制留出区域的上行高度(一行的原点距离其最顶部的距离)
CGFloat RunDelegateGetAscentCallback( void *refCon ){
return 320 * 0.2;
}
CGFloat RunDelegateGetDescentCallback(void *refCon){
return 10 * 2;
}
//控制留出区域的宽度
CGFloat RunDelegateGetWidthCallback(void *refCon){
return 320 ;
}