iOS阅读器实践系列(一)coretext纯文本排版基础
前言:之前做了公司阅读类的App,最近有时间来写一下阅读部分的实现过程,供梳理逻辑,也希望能为后来使用者提供一点思路,如有错误,欢迎指正。
阅读的排版用的是coretext,这篇介绍用coretext实现基本的排版功能。
关于coretext的实现原理,可以查看文档或其他资料,这里就不介绍了,只介绍如何应用coretext来实现一个简单的文本排版功能。
因为coretext是离屏排版的,即在将内容渲染到屏幕之前,内容的排版工作的已经完成了。
排版过程大致过程分为 步:
一、由原始文本数据和需要的相关配置来得到属性字符串。
二、由属性字符串得到CTFramesetter
三、由CTFramesetter和绘制区域得到CTFrame
四、最后将CTFrame渲染到视图的上下文中
1、由原始文本数据和需要的相关配置来得到属性字符串
这一部最关键的是得到相关配置,这些配置可能包括文本对齐方式、段收尾缩进、行高等,下面是一些相关配置属性:
@interface CTFrameParserConfigure : NSObject @property (nonatomic, assign) CGFloat frameWidth;
@property (nonatomic, assign) CGFloat frameHeight; //字体属性
@property (nonatomic, assign) CGFloat wordSpace;
@property (nonatomic, strong) UIColor *textColor;
@property (nonatomic, strong) NSString *fontName;
@property (nonatomic, assign) CGFloat fontSize; //段落属性
@property (nonatomic, assign) CGFloat lineSpace;
@property (nonatomic, assign) CTTextAlignment textAlignment; //文本对齐模式
@property (nonatomic, assign) CGFloat firstlineHeadIndent; //段首行缩进
@property (nonatomic, assign) CGFloat headIndent; //段左侧整体缩进
@property (nonatomic, assign) CGFloat tailIndent; //段尾缩进
@property (nonatomic, assign) CTLineBreakMode lineBreakMode; //换行模式
@property (nonatomic, assign) CGFloat lineHeightMutiple; //行高倍数器(它的值表示原行高的倍数)
@property (nonatomic, assign) CGFloat maxLineHeight; //最大行高限制(0表示无限制,是非负的,行高不能超过此值)
@property (nonatomic, assign) CGFloat minLineHeight; //最小行高限制
@property (nonatomic,assign) CGFloat paragraphBeforeSpace; //段前间距(相对上一段加上的间距)
@property (nonatomic, assign) CGFloat paragraphAfterSpace; //段尾间距(相对下一段加上的间距) @property (nonatomic, assign) CTWritingDirection writeDirection; //书写方向 @property (nonatomic, assign) CGFloat lineSpacingAdjustment; //The space in points added between lines within the paragraph (commonly known as leading). @end
接下来我们要利用这些属性,生成我们需要的配置,在我们根据我们的需要给这些属性赋值以后,利用下面的方法来得到我们需要的配置:
//返回文本所有属性的集合(以字典形式),包括字体、段落等
- (NSDictionary *)attributesWithConfig:(CTFrameParserConfigure *)config
{
//段落属性
CGFloat lineSpacing = config.lineSpace;
CGFloat firstLineIndent = config.firstlineHeadIndent;
CGFloat lineIndent = config.headIndent;
CGFloat tailIndent = config.tailIndent;
CTLineBreakMode lineBreakMode = config.lineBreakMode;
CGFloat lineHeightMutiple = config.lineHeightMutiple;
CGFloat paragraphBeforeSpace = config.paragraphBeforeSpace;
CGFloat paragraphAfterSpace = config.paragraphAfterSpace;
CTWritingDirection writeDirect = config.writeDirection;
CTTextAlignment textAlignment = config.textAlignment;
const CFIndex kNumberOfSettings = ;
CTParagraphStyleSetting paragraphSettings[kNumberOfSettings] = {
{ kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing },
{ kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &lineSpacing },
{ kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(CGFloat), &lineSpacing },
{ kCTParagraphStyleSpecifierAlignment, sizeof(textAlignment), &textAlignment },
{ kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(firstLineIndent), &firstLineIndent },
{ kCTParagraphStyleSpecifierHeadIndent, sizeof(lineIndent), &lineIndent },
{ kCTParagraphStyleSpecifierTailIndent, sizeof(tailIndent), &tailIndent },
{ kCTParagraphStyleSpecifierLineBreakMode, sizeof(lineBreakMode), &lineBreakMode },
{ kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(lineHeightMutiple), &lineHeightMutiple },
{ kCTParagraphStyleSpecifierLineSpacing, sizeof(lineHeightMutiple), &lineHeightMutiple },
{ kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(paragraphBeforeSpace), ¶graphBeforeSpace },
{ kCTParagraphStyleSpecifierParagraphSpacing, sizeof(paragraphAfterSpace), ¶graphAfterSpace },
{ kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(writeDirect), &writeDirect },
}; CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(paragraphSettings, kNumberOfSettings); /**
* 字体属性
*/
CGFloat fontSize = config.fontSize;
//use the postName after iOS10
// CTFontRef fontRef = CTFontCreateWithName((CFStringRef)config.fontName, fontSize, NULL);
CTFontRef fontRef = CTFontCreateWithName(NULL, fontSize, NULL);
// CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"TimesNewRomanPSMT", fontSize, NULL); UIColor * textColor = config.textColor; //设置字体间距
long number = config.wordSpace;
CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, &number); NSMutableDictionary * dict = [NSMutableDictionary dictionary];
dict[(id)kCTForegroundColorAttributeName] = (id)textColor.CGColor;
dict[(id)kCTFontAttributeName] = (__bridge id)fontRef;
dict[(id)kCTKernAttributeName] = (__bridge id)num;
dict[(id)kCTParagraphStyleAttributeName] = (__bridge id)theParagraphRef; CFRelease(num);
CFRelease(theParagraphRef);
CFRelease(fontRef);
return dict;
}
上述过程为先根据上面提供的段落属性值生成段落属性,然后生成字体、字体间距及字体颜色等属性,然后依次将他们存入字典中。
需要注意的地方是 CTParagraphStyleSetting 为C语言的数组,需在创建时指定数组元素个数。
创建的CoreFoundation库中的对象需要手动释放(大部分到create方法生成的对象)
另外在系统升级到iOS10以后,在调节字体大小重新排版时,变得很慢,用Instrument查了一下,发现
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)config.fontName, fontSize, NULL);
这句代码执行时间很长,查找资料发现是字体造成的,iOS10需要用相应的POST NAME。
2、由属性字符串得到CTFramesetter
// 创建 CTFramesetterRef 实例
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)mabStr);
3、由CTFramesetter和绘制区域得到CTFrame
这一步的关键是要得到绘制的区域:
// 获得要绘制的区域的高度
CGSize restrictSize = CGSizeMake(viewWidth, CGFLOAT_MAX);
CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(,), nil, restrictSize, nil);
CGFloat textHeight = coreTextSize.height;
然后生成CTFrame:
//生成绘制的区域
+ (CTFrameRef)createFrameWithFramesetter:(CTFramesetterRef)framesetter frameWidth:(CGFloat)frameWidth stringRange:(CFRange)stringRange orginY:(CGFloat)originY height:(CGFloat)frameHeight
{
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(, originY, frameWidth, frameHeight)); //此时path的位置值都是coretext坐标系下的值 CTFrameRef frame = CTFramesetterCreateFrame(framesetter, stringRange, path, NULL); CFRelease(frame);
CFRelease(path); return frame;
}
这里需要注意的地方就是代码中注释的地方,在排版过程中使用的坐标都是在coretext坐标系下的,即原点在屏幕左下角。
4、将CTFrame渲染到视图的上下文中
这一步是要在视图类的drawRect方法中将上步得到的CTFrame绘制出来:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect]; //将坐标系转换为coretext下的坐标系
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, , self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0); if (ctFrame != nil)
{
CTFrameDraw(ctFrame, context);
}
}
这一步的关键是坐标系的转换,因为ctFrame中包含的绘制区域是在coretext坐标系下,所以在绘制时应先将坐标系转换为coretext坐标系再绘制,才能保证绘制位置正确。
如果渲染时需要精确到行或字体可用CTLine与CTRun,这会在后面介绍。
iOS阅读器实践系列(一)coretext纯文本排版基础的更多相关文章
- 《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块
一.Nginx的HTTP过滤模块特征 一个请求可以被任意个HTTP模块处理: 在普通HTTP模块处理请求完毕并调用ngx_http_send_header()发送HTTP头部或调用ngx_http_o ...
- 微软云平台媒体服务实践系列 2- 使用动态封装为iOS, Android , Windows 等多平台提供视频点播(VoD)方案
文章微软云平台媒体服务实践系列 1- 使用静态封装为iOS, Android 设备实现点播(VoD)方案 介绍了如何针对少数iOS, Android 客户端的场景,出于节约成本的目的使用媒体服务的静 ...
- (android高仿系列)今日头条 --新闻阅读器 (三) 完结 、总结 篇
从写第一篇今日头条高仿系列开始,到现在已经过去了1个多月了,其实大体都做好了,就是迟迟没有放出来,因为我觉得,做这个东西也是有个过程的,我想把这个模仿中一步一步学习的过程,按照自己的思路写下来,在根据 ...
- (android高仿系列)今日头条 --新闻阅读器 (二)
高仿今日头条 --- 第一篇:(android高仿系列)今日头条 --新闻阅读器 (一) 上次,已经完毕了头部新闻分类栏目的拖动效果. 这篇文章是继续去完好APP 今日头条 这个新闻阅读器的其 ...
- Epub 阅读器 - iOS
因项目需求接触的 EPub 阅读器,前前后后尝试了很多库,最后找到了个相对兼容不错的展开了调试;其中对解压缩和数据加载方面进行了改造优化,使其更加的完美; 其大概原理是首先将 epub 文件解压后得到 ...
- SwiftHN阅读器应用IOS源码
SwiftHN是用Swift语言编写的Hacker News阅读器,同时采用了iOS 8最新的API. <ignore_js_op> <ignore_js_op> 详细说明:h ...
- 一步一步学Silverlight 2系列(18):综合实例之RSS阅读器
一步一步学Silverlight 2系列(18):综合实例之RSS阅读器 概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支 ...
- webApp 阅读器项目实践
这是一个webApp 阅读器的项目,是慕课网的老师讲授的一个实战,先给出项目源码在GitHub的地址:https://github.com/yulifromchina/MobileWebReader. ...
- 基于Core Text实现的TXT电子书阅读器
本篇文章的项目地址基于Core Text实现的TXT电子书阅读器. 最近花了一点时间学习了iOS的底层文字处理的框架Core Text.在网上也参考很多资料,具体的资料在文章最后列了出来,有兴趣的可参 ...
随机推荐
- 爬虫技术 -- 基础学习(四)HtmlParser基本认识
利用爬虫技术获取网页源代码后,针对网页抽取出它的特定文本内容,利用正则表达式和抽取工具,能够更好地抽取这些内容. 下面介绍一种抽取工具 -- HtmlParser HtmlParser是一个用来解析H ...
- Koa – 更加强大的下一代 Node.js Web 框架
Koa 是 Express 的开发团队设计的下一代 Web 框架,其目的是为 Web 应用程序提供更小,更具表现力,更坚实的基础.Koa 没有核捆绑任何中间件,并提供了一套优雅的方法,使服务器端开 ...
- vuejs入门小demo-搜索大全
这个demo非常适合入门的同学,不再是简单的todolist.用到的知识点有组件通信,过渡效果,vue-rsource,还有一些基本的vue指令. 先放一张截图: 是不是感觉高端大气上档次呢,演示地址 ...
- ASP.NET MVC 在控制器中接收视图表单POST过来的数据方法
方法一:通过Request.Form [HttpPost] public ActionResult Test() { string id=Reques ...
- 30 分钟快快乐乐学 SQL Performance Tuning
转自:http://www.cnblogs.com/WizardWu/archive/2008/10/27/1320055.html 有些程序员在撰写数据库应用程序时,常专注于 OOP 及各种 fra ...
- EntityFramework 6.1.2-beta2
EntityFramework 6.1.2-beta2 Entity Framework is Microsoft's recommended data access technology for n ...
- 开发(ASP.NET程序)把写代码写至最有面向对象味道
前几天,搬房子时又拿起<重构----改善既有代码的设计>这本书来随便翻来看下,重构Refactoring在开发时,是时常也经常会使用得到. 她确实教我们怎样把写程序写简洁,清楚 好明白,好 ...
- MVC应用程序中管理(更新)上传的文件
实现上传文件功能,有时上传也会操作出错,能让用户有改正有机会,开发上传文件能有更新的功能. 文件上传时,如果是存储于应用程序某一目录的话,在更新时需要了解一些流程,先是删除旧文件,更新数据表相关信息, ...
- 【.NET框架】Dapper ORM 用法—Net下无敌的ORM
假如你喜欢原生的Sql语句,又喜欢ORM的简单,那你一定会喜欢上Dapper这款ROM.点击下载 Dapper的优势: 1,Dapper是一个轻型的ORM类.代码就一个SqlMapper.cs文件,编 ...
- 【AngularJS学习笔记】01 指令、服务和过滤器
AngularJS 指令是扩展的 HTML 属性,带有前缀 ng-. 比如: ng-app 指令初始化一个 AngularJS 应用程序.注意ng-app一般为空,如果值不为空,就得加这样一句代码va ...