内容简介:1. CoreText框架基础CoreText是Mac OS和iOS系统中处理文本的low-level API, 不管是使用OC还是swift, 实际我们使用CoreText都还是间接或直接使用C语言在写代码。CoreText是iOS和Mac OS中文本处理的根基, TextKit和WebKit都是构建于其上。常用类、属性
1. CoreText框架基础
CoreText是Mac OS和iOS系统中处理文本的low-level API, 不管是使用OC还是swift, 实际我们使用CoreText都还是间接或直接使用 C语言 在写代码。CoreText是iOS和Mac OS中文本处理的根基, TextKit和WebKit都是构建于其上。
常用类、属性
CTFrameRef
CTFramesetterRef
CTLineRef
CTRunRef
CTTypesetterRef
CTGlyphInfoRef (NSGlyphInfo)
CTParagraphStyleRef (NSParagraphStyle)
CTFontRef (UIFont)
CFArrayRef (NSArray)
分析:
coreText 属于怎样一套API?
字体结构:
当我们进行字体绘制的时候很重要。
CTRun、CTFrame、CTLine
- CTFrame可以想象成画布, 画布的大小范围由CGPath决定 - CTFrame由很多CTLine组成, CTLine表示为一行 - CTLine由多个CTRun组成, CTRun相当于一行中的多个块, 但是CTRun不需要你自己创建, 由NSAttributedString的属性决定, 系统自动生成。每个CTRun对应不同属性。 - CTFramesetter是一个工厂, 创建CTFrame, 一个界面上可以有多个CTFrame - CTFrame就是一个基本画布,然后一行一行绘制。 CoreText会自动根据传入的NSAttributedString属性创建CTRun,包括字体样式,颜色,间距等
更多详细的基础知识见末尾参考。
流程
如下图所示,这就是CoreText的基本处理流程:
1、创建AttributedString,定义样式
2、通过 CFAttributedStringRef 生成 CTFramesetter
3、通过CTFramesetter得到CTFrame
4、绘制 (CTFrameDraw)
5、如果有图片存在,先在AttributedString 对应位置添加占位符
6、通过回调函数确定图片的宽高(CTRunDelegateCallbacks)
7、遍历到对应CTRun上、获取对应CGRect、绘制图片(CGContextDrawImage)
2. 基本的文本样式实操
CoreText是需要自己处理绘制,不像UILabel等最上层的控件 ,所以我们必须在drawRect中绘制,为了更好地使用,我们稍微封装一下,自定义一个UIView。
我们在使用上层的控件时,坐标系的原点在左上角,而底层的Core Graphics的坐标系原点则是在左下角,以下是一个最基本的绘制示例:
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
//step 1:获取当前画布的上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//step 2:
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
//step 3:
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"xXHhofiyYI这是一段中文,前面是大小写"];
//step 4:
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attributedString length]), path, NULL);
//step 5:
CTFrameDraw(frame,context);
//step 6:
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);
//使用Create函数建立的对象引用,必须要使用CFRelease掉。
}
效果如下:
结果分析:发现文案是反的。原因就是因为coreText的坐标系是和UIKit的坐标系不一样的:
如上图,CoreText是基于CoreGraphics的,所以坐标系原点是左下角,我们需要进行翻转。将Y轴从向上转换为向下。
CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGContextTranslateCTM(context, 0, self.bounds.size.height); CGContextScaleCTM(context, 1.0, -1.0);
翻转后,下面来进行一个最基本的富文本示例:
step 4 添加
[attr addAttribute:(NSString *)kCTBackgroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, 10)];
[attr addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id _Nonnull)(fontRef) range:NSMakeRange(0, 10)];
效果如下:
上面的绘制方式是基于CTFrame绘制,还可以按行和按run绘制:
按CTLine绘制
// 1.获得CTLine数组 let lines = CTFrameGetLines(frame) // 2.获得行数 let numberOfLines = CFArrayGetCount(lines) // 3.获得每一行的origin, CoreText的origin是在字形的baseLine处的, 请参考字形图 var lineOrigins = [CGPoint](count: numberOfLines, repeatedValue: CGPointZero) CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), &lineOrigins) // 4.遍历每一行进行绘制 for index in 0.. <numberoflines nbsp="" let="" origin="" 参考:="" http:="" swifter="" tips="" unsafe="" line="" 设置每一行的位置="" cgcontextsettextposition="" context="" x="" y="" 开始一行的绘制="" ctlinedraw=""></numberoflines>
按CTRun绘制
用下面函数替换CTLineDraw(line, context)这一句就可以了, 效果也如上面。
// 画一行
func drawLine(line: CTLine, context: CGContext) {
let runs = CTLineGetGlyphRuns(line) as Array
runs.forEach { run in
CTRunDraw(run as! CTRun, context, CFRangeMake(0, 0))
}
}
}
3. 图文混排
CoreText本身是不提供UIImage的绘制,所以UIImage肯定只能通过Core Graphics绘制,但是绘制时双必须要知道此绘制单元的长宽,庆幸的是CoreText绘制的最小单元CTRun提供了CTRunDelegate,也就是当设置了kCTRunDelegateAttributeName过后,CTRun的绘制时所需的参考(长宽等)将可从委托中获取,我们即可通过此方法实现图片的绘制。在需要绘制图片的位置,提前预留空白占位。
CTRun有几个委托用以实现CTRun的几个参数的获取。
以下是CTRunDelegateCallbacks的几个委托代理 。
typedef struct
{
CFIndex version;
CTRunDelegateDeallocateCallback dealloc;
CTRunDelegateGetAscentCallback getAscent;
CTRunDelegateGetDescentCallback getDescent;
CTRunDelegateGetWidthCallback getWidth;
} CTRunDelegateCallbacks;
以下是一个最基本的图片绘制原型:遍历查询图片,即查找含有imgName attribute的CTRun,并绘制。
step 4 前面添加:
CTRunDelegateCallbacks imageCallBacks;
imageCallBacks.version = kCTRunDelegateCurrentVersion;
imageCallBacks.dealloc = ImgRunDelegateDeallocCallback;
imageCallBacks.getAscent = ImgRunDelegateGetAscentCallback;
imageCallBacks.getDescent = ImgRunDelegateGetDescentCallback;
imageCallBacks.getWidth = ImgRunDelegateGetWidthCallback;
NSString *imgName = @"test.jpg";
CTRunDelegateRef imgRunDelegate = CTRunDelegateCreate(&imageCallBacks, (__bridge void * _Nullable)(imgName));//我们也可以传入其它参数
NSMutableAttributedString *imgAttributedStr = [[NSMutableAttributedString alloc]initWithString:@" "];
[imgAttributedStr addAttribute:(NSString *)kCTRunDelegateAttributeName value:(__bridge id)imgRunDelegate range:NSMakeRange(0, 1)];
CFRelease(imgRunDelegate);
#define kImgName @"imgName"
//图片占位符添加
[imgAttributedStr addAttribute:kImgName value:imgName range:NSMakeRange(0, 1)];
[attributedString insertAttributedString:imgAttributedStr atIndex:30];
step 5 后面添加:
//绘制图片
CFArrayRef lines = CTFrameGetLines(frame);
CGPoint lineOrigins[CFArrayGetCount(lines)];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), lineOrigins);//获取第行的起始点
for (int i = 0; i < CFArrayGetCount(lines); i++) {
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGFloat lineAscent;//上缘线
CGFloat lineDescent;//下缘线
CGFloat lineLeading;//行间距
CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);//获取此行的字形参数
//获取此行中每个CTRun
CFArrayRef runs = CTLineGetGlyphRuns(line);
for(int j = 0;j< CFArrayGetCount(runs);j++){
CGFloat runAscent;//此CTRun上缘线
CGFloat runDescent;//此CTRun下缘线
CGPoint lineOrigin = lineOrigins[i];//此行起点
CTRunRef run = CFArrayGetValueAtIndex(runs, j);//获取此CTRun
NSDictionary *attributes = (NSDictionary *)CTRunGetAttributes(run);
CGRect runRect;
//获取此CTRun的上缘线,下缘线,并由此获取CTRun和宽度
runRect.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &runAscent, &runDescent, NULL);
//CTRun的X坐标
CGFloat runOrgX = lineOrigin.x + CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
runRect = CGRectMake(runOrgX,lineOrigin.y-runDescent,runRect.size.width,runAscent+runDescent );
NSString *imgName = [attributes objectForKey:kImgName];
if (imgName) {
UIImage *image = [UIImage imageNamed:imgName];
if(image){
CGRect imageRect ;
imageRect.size = image.size;
imageRect.origin.x = runRect.origin.x + lineOrigin.x;
imageRect.origin.y = lineOrigin.y;
CGContextDrawImage(context, imageRect, image.CGImage);
}
}
}
}
代理函数:
#pragma mark - CTRunDelegateCallbacks
void ImgRunDelegateDeallocCallback( void* refCon ){
}
CGFloat ImgRunDelegateGetAscentCallback( void *refCon ){
NSString *imageName = (__bridge NSString *)refCon;
return [UIImage imageNamed:imageName].size.height;
}
CGFloat ImgRunDelegateGetDescentCallback(void *refCon){
return 0;
}
CGFloat ImgRunDelegateGetWidthCallback(void *refCon){
NSString *imageName = (__bridge NSString *)refCon;
return [UIImage imageNamed:imageName].size.width;
}
效果如下:
基于以上这个原型,我们可以封装一个比较完整的富文本控件,比如定义HTML协议或者JSON,然后在内部进行解析,然后根据类型与相应的属性进行绘制。
4. 图片点击事件
CoreText就是将内容绘制到画布上,自然没有事件处理,我们要实现图片与链接的点击效果就需要使用触摸事件了。当点击的位置在图片的Rect中,那我们做相应的操作即可,所以基本步骤如下:
记录所有图片所在画布中作为一个CTRun的位置 -> 获取每个图片所在画布中所占的Rect矩形区域 -> 当点击事件发生时,判断点击的点是否在某个需要处理的图片Rect内。
这里为了演示的简单,我们直接在drawRect中记录图片的相应坐标,但是一般我们会在CTRichView渲染之前对数据进行相应的处理,比如处理传入的样式数据、记录图片与链接等信息。
用于记录图片信息类
@interface CTImageData : NSObject @property (nonatomic,strong) NSString *imgHolder; @property (nonatomic,strong) NSURL *imgPath; @property (nonatomic) NSInteger idx; @property (nonatomic) CGRect imageRect; @end
//记录图片信息
//以下操作仅仅是演示示例,实战时请在渲染之前处理数据,做到最佳实践。
if(!_imageDataArray){
_imageDataArray = [[NSMutableArray alloc]init];
}
BOOL imgExist = NO;
for (CTImageData *ctImageData in _imageDataArray) {
if (ctImageData.idx == idx) {
imgExist = YES;
break;
}
}
if(!imgExist){
CTImageData *ctImageData = [[CTImageData alloc]init];
ctImageData.imgHolder = imgName;
ctImageData.imageRect = imageRect;
ctImageData.idx = idx;
[_imageDataArray addObject:ctImageData];
}
- (void)setupEvents{
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(userTapGestureDetected:)];
[self addGestureRecognizer:tapRecognizer];
self.userInteractionEnabled = YES;
}
- (void)userTapGestureDetected:(UIGestureRecognizer *)recognizer{
CGPoint point = [recognizer locationInView:self];
//先判断是否是点击的图片Rect
for(CTImageData *imageData in _imageDataArray){
CGRect imageRect = imageData.imageRect;
CGFloat imageOriginY = self.bounds.size.height - imageRect.origin.y - imageRect.size.height;
CGRect rect = CGRectMake(imageRect.origin.x,imageOriginY, imageRect.size.width, imageRect.size.height);
if(CGRectContainsPoint(rect, point)){
NSLog(@"tap image handle");
return;
}
}
//再判断链接
}
5. 链接点击事件
记录链接信息类
@interface CTLinkData : NSObject @property (nonatomic ,strong) NSString *text; @property (nonatomic ,strong) NSString *url; @property (nonatomic ,assign) NSRange range; @end
记录链接信息
if(!_linkDataArray){
_linkDataArray = [[NSMutableArray alloc]init];
}
CTLinkData *ctLinkData = [[CTLinkData alloc]init];
ctLinkData.text = [attributedString.string substringWithRange:linkRange];
ctLinkData.url = @"http://www.baidu.com";
ctLinkData.range = linkRange;
[_linkDataArray addObject:ctLinkData];
处理链接事件
- (void)userTapGestureDetected:(UIGestureRecognizer *)recognizer{
CGPoint point = [recognizer locationInView:self];
//先判断是否是点击的图片Rect
//......
//再判断链接
CFIndex idx = [self touchPointOffset:point];
if (idx != -1) {
for(CTLinkData *linkData in _linkDataArray){
if (NSLocationInRange(idx, linkData.range)) {
NSLog(@"tap link handle,url:%@",linkData.url);
break;
}
}
}
}
根据点击点获取字符串偏移
- (CFIndex)touchPointOffset:(CGPoint)point{
//获取所有行
CFArrayRef lines = CTFrameGetLines(_ctFrame);
if(lines == nil){
return -1;
}
CFIndex count = CFArrayGetCount(lines);
//获取每行起点
CGPoint origins[count];
CTFrameGetLineOrigins(_ctFrame, CFRangeMake(0, 0), origins);
//Flip
CGAffineTransform transform = CGAffineTransformMakeTranslation(0, self.bounds.size.height);
transform = CGAffineTransformScale(transform, 1.f, -1.f);
CFIndex idx = -1;
for (int i = 0; i< count; i++) {
CGPoint lineOrigin = origins[i];
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
//获取每一行Rect
CGFloat ascent = 0.0f;
CGFloat descent = 0.0f;
CGFloat leading = 0.0f;
CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
CGRect lineRect = CGRectMake(lineOrigin.x, lineOrigin.y - descent, width, ascent + descent);
lineRect = CGRectApplyAffineTransform(lineRect, transform);
if(CGRectContainsPoint(lineRect,point)){
//将point相对于view的坐标转换为相对于该行的坐标
CGPoint linePoint = CGPointMake(point.x-lineRect.origin.x, point.y-lineRect.origin.y);
//根据当前行的坐标获取相对整个CoreText串的偏移
idx = CTLineGetStringIndexForPosition(line, linePoint);
}
}
return idx;
}
6、微博类型富文本实现异步绘制
当我们涉及到图文混排时候的高度计算
http://www.jianshu.com/p/a7f55e456539
YYLable
1、解析@、超链接、图片、表情
2、逐行逐Run异步绘制,
3、点击高亮背景绘制
3、点击效果、事件
参考
Text Programming Guide for iOS
作者:就叫yang
链接:https://www.jianshu.com/p/1c3d0936bba6
以上所述就是小编给大家介绍的《iOS开发CoreText使用说明书》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 微前端说明书
- 给卷积神经网络“修理工”的一份“说明书”
- 你好,SMI: service mesh 互操作性说明书
- 如何组织一个全方位的软件项目工作任务说明书
- Google 发布其非 Linux 系操作系统 Fuchsia 说明书
- Spring Cloud F & Spring Boot 2.0 版本升级说明书
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Creative Curve
Allen Gannett / Knopf Doubleday Publishing Group / 2018-6-12
Big data entrepreneur Allen Gannett overturns the mythology around creative genius, and reveals the science and secrets behind achieving breakout commercial success in any field. We have been s......一起来看看 《The Creative Curve》 这本书的介绍吧!