CoreText原理及基本使用方法
关于富文本的排版也是现在的一个技术点,以下是近日关于CoreText的学习记录以及个人理解,希望能对正在学习CoreText的朋友起到帮助。
1.框架坐标系
首先让我们先来看看CoreText坐标系和UIKit坐标系的不同
从图中可看出CoreText坐标系是以左下角为坐标原点,而我们常使用的UIKit是以左上角为坐标原点,因此在CoreText中的布局完成后需要对其坐标系进行转换,否则直接绘制出现位置反转的镜像情况。在通常情况下我们一般做法是直接获取当前上下文。并将当前上下文的坐标系转换为CoreText坐标系,再将布局好的CoreText绘制到当前上下文中即可。以下是此种方案的实现逻辑
//获取当前上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//翻转坐标系步骤
//设置当前文本矩阵
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
//文本沿y轴移动
CGContextTranslateCTM(context, , self.bounds.size.height);
//文本翻转成为CoreText坐标系
CGContextScaleCTM(context, , -);
2.CoreText文本布局
CoreText的布局同UIKit布局不太相同,CoreText中布局大体思路是确定文本绘制区域,接着得到文本实际大小(frame)。其具体步骤如下:
1.首先要确定布局时绘制的区域,其对应的类为CG(Mutable)PathRef
2.设置文本内容,其对应的类为NS(Mutable)AttributedString
3.根据文本内容配置其CTFramesetterRef
4.利用CTFramesetterRef得到CTFrame
有了以上具体步骤那我们开始实际的代码操作:
//1.创建绘制区域,显示的区域可以用CGMUtablePathRef生成任意的形状
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(, , self.bounds.size.width - , self.bounds.size.height - ));
//2.创建需要绘制的文字
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:@"\tWhen I will learn CoreText, i think it will hard for me.But it is easy.\n\tIn fact,if you bengin learn, you can know that every thing is easy when you start.you just need some knowlages"];
//3.根据AttString生成CTFramesetterRef
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(, [attString length]), path, NULL);
2.1文本属性设置
此处我们使用的是NSmutableAttributedString来进行文本设置,是因为我们可以很方便的设置其属性,以下为部分属性设置
//设置绘制的文本内容
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:@"\tWhen I will learn CoreText, i think it will hard for me.But it is easy.\n\tIn fact,if you bengin learn, you can know that every thing is easy when you start.you just need some knowlages"];
//设置文本内容的属性
//1设置部分文字颜色
[attString addAttribute:(id)kCTForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange( , )];
//2设置部分文字字体
CGFloat fontSize = ;
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
[attString addAttribute:(id)kCTFontAttributeName value:(__bridge id)fontRef range:NSMakeRange(, )];
//3设置斜体
CTFontRef italicFontRef = CTFontCreateWithName((CFStringRef)[UIFont italicSystemFontOfSize:].fontName, , NULL);
[attString addAttribute:(id)kCTFontAttributeName value:(__bridge id)italicFontRef range:NSMakeRange(, )];
//4设置下划线
[attString addAttribute:(id)kCTUnderlineStyleAttributeName value:(id)[NSNumber numberWithInteger:kCTUnderlineStyleDouble] range:NSMakeRange(, )];
//5设置下划线颜色
[attString addAttribute:(id)kCTUnderlineColorAttributeName value:(id)[UIColor greenColor].CGColor range:NSMakeRange(, )];
//6设置空心字
long number1 = ;
CFNumberRef numRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, &number1);
[attString addAttribute:(id)kCTStrokeWidthAttributeName value:(__bridge id)numRef range:NSMakeRange(, )];
//7设置字体间距
long number = ;
CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, &number);
[attString addAttribute:(id)kCTKernAttributeName value:(__bridge id)num range:NSMakeRange(, )];
//8设置行间距
CGFloat lineSpacing = ;
const CFIndex kNumberOfSettings = ;
CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
{kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing},
{kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &lineSpacing},
{kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &lineSpacing}
};
CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);
[attString addAttribute:(id)kCTParagraphStyleAttributeName value:(__bridge id)theParagraphRef range:NSMakeRange(, [attString length])];
2.2图片文本内容
图片宽高在工程中都需要加载后才知道,而在文本绘制中需要直接留出其位置再进行绘制,所以图片的宽高都是在数据中保存好的,此处笔者用固定值来表示其宽高。为了留出其位置我们需要用空白的字符来做占位符使用。为了知道其图片绘制的位置(即空白占位符位置)我们需要设置代理才能够得知图片绘制位置。具体步骤如下:
1.创建CTRunDelegateCallbacks 回调函数:通过回调函数来确定图片绘制的宽高
2.创建空白占位字符
3.设置CTRunDeleagte:通过代理来找到该字符串,并确定图片绘制的原点
下面让我们来看看具体的实现代码
#pragma mark - CTRunDelegateCallbacks Method
//此处使用的字典结构来存储数值
static CGFloat heightCallBack(void *ref) {
return [(NSNumber *)[(__bridge NSDictionary *)ref objectForKey:@"height"] floatValue];
}
static CGFloat descentCallBack (void *ref) {
return ;
}
static CGFloat widthCallBack (void *ref) {
return [(NSNumber *)[(__bridge NSDictionary *)ref objectForKey:@"width"] floatValue];
} #pragma mark - 空白占位符及代理设置
//CTRunDelegateCallBacks:用于保存指针的结构体,由CTRun delegate进行回调
CTRunDelegateCallbacks callbacks;
memset(&callbacks, , sizeof(CTRunDelegateCallbacks));
callbacks.version = kCTRunDelegateVersion1;
callbacks.getAscent = heightCallBack;
callbacks.getDescent = descentCallBack;
callbacks.getWidth = widthCallBack;
//图片信息字典
NSDictionary *imgInfoDic = @{@"width":@,@"height":@};
//创建CTRunDelegate的代理
CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void*)imgInfoDic);
//使用oxFFFC作为空白占位符
unichar objectReplacementChar = 0xFFFC;
NSString *content = [NSString stringWithCharacters:&objectReplacementChar length:];
NSMutableAttributedString *space = [[NSMutableAttributedString alloc] initWithString:content];
//设置代理
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)space, CFRangeMake(, ), kCTRunDelegateAttributeName, delegate);
在贴出获取图片位置代码前,还需要补充一个理论知识,在CoreText中所有的布局都是基于行(CTLineRef)来进行的,每行都是一个CTLineRef对象,在每行当中又包含多个属性(CTRunRef)每行的属性可设置代理,如上面笔者就是对空白占位符这个CTRunRef设置了代理。下面为CTLineRef和CTRunRef的示意图
明白此中原理后便可以上代码了解具体怎么实现
//获取CTLine数组
NSArray *lines = (NSArray *)CTFrameGetLines(ctframe);
NSInteger lineCount = lines.count;
CGPoint lineOrigins[lineCount];
CTFrameGetLineOrigins(ctframe, CFRangeMake(, ), lineOrigins);
//遍历每一个CTline
for (NSInteger i = ; i < lineCount; i ++) {
CTLineRef line = (__bridge CTLineRef)lines[i];
NSArray *runObjArray = (NSArray *)CTLineGetGlyphRuns(line);
//遍历每个CTLine中的CTRun找到空白字符的delegate
for (id runObj in runObjArray) {
CTRunRef run = (__bridge CTRunRef)runObj;
NSDictionary *runAttributes = (NSDictionary *)CTRunGetAttributes(run);
CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForKey:(id)kCTRunDelegateAttributeName];
if (delegate == nil) {
continue;
}
NSDictionary *metaDic = CTRunDelegateGetRefCon(delegate);
if (![metaDic isKindOfClass:[NSDictionary class]]) {
continue;
}
//找到代理后开始计算空白字符的位置
CGRect runBounds;
CGFloat ascent;
CGFloat descent; runBounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(, ), &ascent, &descent, NULL);
runBounds.size.height = ascent + descent;
//计算在行当中的x偏移量
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
runBounds.origin.x = lineOrigins[i].x + xOffset;
runBounds.origin.y = lineOrigins[i].y - descent;
//获得ctframe的绘制区域
CGPathRef pathRef = CTFrameGetPath(ctframe);
//计算此绘制区域的范围
CGRect colRect = CGPathGetBoundingBox(pathRef);
//计算在此区域中空白字符的位置
CGRect delegateBounds= CGRectOffset(runBounds, colRect.origin.x, colRect.origin.y);
//记录空白字符位置
_imageRect = delegateBounds;
//返回空白字符位置
return delegateBounds;
}
}
//若没有找到对应的代理则返回空位置
return CGRectZero;
3.绘制文本内容
绘制文本内容相对来说就比较简单了,只需要在2句代码即可搞定
//绘制文本
CTFrameDraw(frame, context);
//绘制图像
UIImage *image = [UIImage imageNamed:@"boat.jpg"];
CGContextDrawImage(context, _imageRect, image.CGImage);
4.总结
到此一个基本的CoreText布局排版已完成(注意绘制文本需要在drawRect中进行)。这里放上一个demo链接https://github.com/PurpleSweetPotatoes/CoreText_Learn.git,demo中包含了富文本点击事件的处理,是对《iOS开发进阶》书中CoreText的示例的整理,其中的逻辑思路就不在此赘述了,在demo中有详细的注释,朋友们可以直接下载学习。若文章或demo中有任何错误欢迎指正,谢谢!
CoreText原理及基本使用方法的更多相关文章
- 深入解析ThreadLocal 详解、实现原理、使用场景方法以及内存泄漏防范 多线程中篇(十七)
简介 从名称看,ThreadLocal 也就是thread和local的组合,也就是一个thread有一个local的变量副本 ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个 ...
- String类原理分析及部分方法
//String类原理分析及部分方法 //http://www.cnblogs.com/vamei/archive/2013/04/08/3000914.html //http://www.cnblo ...
- .NET应用程序调试—原理、工具、方法
阅读目录: 1.背景介绍 2.基本原理(Windows调试工具箱..NET调试扩展SOS.DLL.SOSEX.DLL) 2.1.Windows调试工具箱 2.2..NET调试扩展包,SOS.DLL.S ...
- javascript数组(1) ——sort的工作原理及其他数组排序方法
一说到数组排序,最直观的想法就是用sort啊! 请问不用使用sort方法还可以使用什么方法进行数组排序? 比如 : 快速排序法.合并排序法.冒泡排序法.选择排序法.插入排序法.布尔排序法.交互排序. ...
- Websocket(一)——原理及基本属性和方法
初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处? 答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起. 举例 ...
- 细菌多位点序列分型(Multilocus sequence typing,MLST)的原理及分型方法
摘 要: 多位点序列分型(MLST)是一种基于核酸序列测定的细菌分型方法,通过PCR扩增多个管家基因内部片段,测定其序列,分析菌株的变异,从而进行分型.MLST被广泛应用于病原菌.环境菌和真核生物中. ...
- 牛客网Java刷题知识点之字符流缓冲区、BufferedWriter、BufferedReader、BufferedReader-readLine方法原理、自定义MyBufferedReader-read方法、自定义MyBufferedReader-readLine方法
不多说,直接上干货! 把提高效率的动作,封装成一个对象.即把缓冲区封装成一个对象. 就是在一个类里封装一个数组,能对流锁操作数据进行缓存. 什么是字符流缓冲区? 善于使用字符流缓冲区,减轻负担,提高下 ...
- android黑科技系列——分析某直播App的协议加密原理以及调用加密方法进行协议参数构造
一.前言 随着直播技术火爆之后,各家都出了直播app,早期直播app的各种请求协议的参数信息都没有做任何加密措施,但是慢慢的有人开始利用这个后门开始弄刷粉关注工具,可以让一个新生的小花旦分分钟变成网红 ...
- SpringMVC+Spring+Hibernate框架整合原理,作用及使用方法
转自:https://blog.csdn.net/bieleyang/article/details/77862042 SSM框架是spring MVC ,spring和mybatis框架的整合,是标 ...
随机推荐
- whether the computers in a cluster share access to the same disks
COMPUTER ORGANIZATION AND ARCHITECTURE DESIGNING FOR PERFORMANCE NINTH EDITION In the literature, cl ...
- http://highscalability.com/blog/2015/5/18/how-mysql-is-able-to-scale-to-200-million-qps-mysql-cluster.html
http://highscalability.com/blog/2015/5/18/how-mysql-is-able-to-scale-to-200-million-qps-mysql-cluste ...
- Android jni系统变量、函数、接口定义汇总
在做Android jni开发时,jni为我们提供了哪些函数.接口.变量,有时候一头雾水,今天就把jni.h中定义的所有内容列出来,供自己查阅: /* * Copyright (C) 2006 The ...
- Wordpress基础:文章和页面的区别
页面: 页面是你可以单独建立一个固定页面,可以作为留言板,或者通知的单页面,发布之后是固定的网址. 页面并不能被分类.亦不能拥有标签,但是它们可以有层级关系.您可将页面附属在另一个页面之下. 对应模板 ...
- Java学习-042-获取目录文件列表(当前,级联)
以下三个场景,在我们日常的测试开发中经常遇到: 软件自动化测试,在进行参数测试时,我们通常将所有相似功能的参数文件统一放在一个目录中,在自动化程序启动的时候,获取资源参数文件夹中所有参数文件,然后解析 ...
- Linux Shell编程基础
在学习Linux BASH Shell编程的过程中,发现由于不经常用,所以很多东西很容易忘记,所以写篇文章来记录一下 ls 显示当前路径下的文件,常用的有 -l 显示长格式 -a 显示所有包括隐 ...
- 传入一个label或者button,传入5s,6和6+的文字尺寸 快速定义文字大小
func isIphone6() -> Bool { { return true } else { return false } } func isIphone6Plus() -> Boo ...
- ios - 自动布局框架编写(更多功能完善中)
之前用的storyboard以及xib挺多的,最近看到朋友用第三方框架---自动布局约束框架在添加控件约束的时候老实报错.后来自己就试了试纯代码创建以及约束控件.但是纯代码约束一个控件还可以,如果约束 ...
- Linux下添加用户及用户组
创建用户组hdpgroup: $ sudo addgroup hdpgroup 如果用户hdp不存在,把hdp添加到hdpgroup用户组: $ sudo adduser --force -ingro ...
- Velocity(7)——#foreach指令
首先是#foreach的简单示例: #foreach( $elem in $allElems) $elem</br> #end 上面这个例子中,$allElems可以是一个Vector,一 ...