CoreText入坑一
CoreText是Mac OS和iOS系统中处理文本的low-level API, 不管是使用OC还是swift, 实际我们使用CoreText都还是间接或直接使用C语言在写代码。CoreText是iOS和Mac OS中文本处理的根基, TextKit和WebKit都是构建于其上。
一. 基础
1.在使用CoreText编写代码之前, 需要先了解一些基础知识。下图是CoreText的基础框架

- CTFrame可以想象成画布, 画布的大小范围由CGPath决定
- CTFrame由很多CTLine组成, CTLine表示为一行
- CTLine由多个CTRun组成, CTRun相当于一行中的多个块, 但是CTRun不需要你自己创建, 由NSAttributedString的属性决定, 系统自动生成。每个CTRun对应不同属性
- CTFramesetter是一个工厂, 创建CTFrame, 一个界面上可以有多个CTFrame
2.文字的样式包括很多, 而每个字符的显示要归功于字体, 而字体包括很多基础知识, 比如磅值, 样式, 基线, 连字等等, 这里就不做更多介绍, 推荐两篇文章阅读。
CoreText基础概念
CoreText入门
在这里, 贴出CoreText基础概念文中关于字体结构的图(图片版权归此文作者)


二. 使用基本步骤
新建一个UIView, 在view的drawRect函数中按步骤写入下面的代码
- 1.获取当前上下文
let context = UIGraphicsGetCurrentContext()
- 2.转换坐标系
CGContextSetTextMatrix(context, CGAffineTransformIdentity)
CGContextTranslateCTM(context, 0, self.bounds.size.height)
CGContextScaleCTM(context, 1.0, -1.0)
- 3.初始化路径
let path = CGPathCreateWithRect(self.bounds, nil)
- 4.初始化字符串
let attrString = NSMutableAttributedString(string: "Hello CoreText")
- 5.初始化framesetter
let framesetter = CTFramesetterCreateWithAttributedString(attrString)
- 6.绘制frame
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrString.length), path, nil)
CTFrameDraw(frame, context!)
绘制的步骤完成了, 然后在ViewController里面将此view加入到ViewController中, 记得将view的背景色设置为白色, 那么效果就应该如下了

文字绘制出来了, 这就是CoreText使用最基本的步骤了。
三.简单的富文本Label
上面简单的绘制步骤中, 最后一步绘制frame, 是将整个frame当做一块绘制, 至于什么换行, 行中的样式什么的都是系统自己决定了。在开始之前, 我们将这个绘制frame改成我们自己一行一行, 甚至一个run一个run的绘制
- 按行绘制
// 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 {let origin = lineOrigins[index]// 参考: http://swifter.tips/unsafe/let line = unsafeBitCast(CFArrayGetValueAtIndex(lines, index), CTLine.self)// 设置每一行的位置CGContextSetTextPosition(context, origin.x, origin.y)// 开始一行的绘制CTLineDraw(line, context)
}
将最后一步改成按行绘制, 最终得到的效果也和按frame绘制一样的, 接下来看下按Run绘制
- 按Run绘制
// 画一行
func drawLine(line: CTLine, context: CGContext) {let runs = CTLineGetGlyphRuns(line) as Arrayruns.forEach { run inCTRunDraw(run as! CTRun, context, CFRangeMake(0, 0))}}
}
用此函数替换CTLineDraw(line, context)这一句就可以了, 效果也如上面。
那么接下来实现一个简单的富文本Label, 将上面的view改名为TULabel
- 声明一个富文本变量给此Label
var attributedText: NSAttributedString?
- 将上面的第4步注释掉, 初始化framesetter的字符串直接传入此变量, 至于后面的绘制你可以用任意一种, 这样TULabel就可以实现一部分富文本了, 在controller中创建一个TULabel, 然后来个NSMutableAttributedString实例赋值给TULabel.attributedText, 下面列出此时可用的富文本样式
let attributedText = NSMutableAttributedString(string: ...)// CoreText支持的属性// 字体颜色
attributedText.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: NSMakeRange(0, 10))// 下划线
let underlineStyles = [NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue,NSUnderlineColorAttributeName: UIColor.orangeColor()]
attributedText.addAttributes(underlineStyles, range: NSMakeRange(10, 10))// 字体
attributedText.addAttribute(NSFontAttributeName, value: UIFont.boldSystemFontOfSize(50), range: NSMakeRange(20, 10))// 描边(Stroke):组成字符的线或曲线。可以加粗或改变字符形状
let strokeStyles = [NSStrokeWidthAttributeName: 10,NSStrokeColorAttributeName: UIColor.blueColor()]
attributedText.addAttributes(strokeStyles, range: NSMakeRange(40, 20))// 横竖文本
attributedText.addAttribute(NSVerticalGlyphFormAttributeName, value: 0, range: NSMakeRange(70, 10))// 字符间隔
attributedText.addAttribute(NSKernAttributeName, value: 5, range: NSMakeRange(90, 10))// 段落样式
let paragraphStyle = NSMutableParagraphStyle()//对齐模式
paragraphStyle.alignment = .Center//换行裁剪模式
paragraphStyle.lineBreakMode = .ByWordWrapping// 行间距
paragraphStyle.lineSpacing = 5.0// 字符间距
paragraphStyle.paragraphSpacing = 2.0attributedText.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSRange(location: 0, length: attributedText.length))
此时, 你就会看到如下效果

- 效果出来了, 你是否就满足了。那其他平常可以使用的样式要怎么样使用CoreText来实现了? 我们就先实现一个样式--删除线, 将上面的drawLine函数改成如下
// 画一行
func drawLine(line: CTLine, context: CGContext) {let runs = CTLineGetGlyphRuns(line) as Arrayruns.forEach { run inCTRunDraw(run as! CTRun, context, CFRangeMake(0, 0))// 获得run的所有样式let attributes = CTRunGetAttributes(run as! CTRun) as NSDictionary// 判断是run是否含有删除线样式if nil != attributes[NSStrikethroughStyleAttributeName] {// 开始画删除线drawStrikethroughStyle(run as! CTRun, attributes: attributes, context: context)}}
}
- 当然, 你要将CTLineDraw(line, context)换成自定义的画行函数drawLine(line, context: context), 那么接下来就是画删除线了
// 画删除线, 这里涉及到字体相关知识, 请参考第二节, 画删除线实际画在字的中间, 而字体的高度不一样, 实际是画在x高度的一半位置
func drawStrikethroughStyle(run: CTRun, attributes: NSDictionary, context: CGContext) {// 1.获取删除线样式let styleRef = attributes[NSStrikethroughStyleAttributeName]var style: NSUnderlineStyle = .StyleNoneCFNumberGetValue(styleRef as! CFNumber, CFNumberType.SInt64Type, &style)// 如果定义为none, 就不用画了guard style != .StyleNone else {return}// 2.获得画线的宽度var lineWidth: CGFloat = 1if (style.rawValue & NSUnderlineStyle.StyleThick.rawValue) == NSUnderlineStyle.StyleThick.rawValue {lineWidth *= 2}CGContextSetLineWidth(context, lineWidth)// 3.获取画线的起点var firstPosition = CGPointZerolet firstGlyphPosition = CTRunGetPositionsPtr(run)if nil == firstGlyphPosition {let positions = UnsafeMutablePointer<CGPoint>.alloc(1)positions.initialize(CGPointZero)CTRunGetPositions(run, CFRangeMake(0, 0), positions)firstPosition = positions.memorypositions.destroy()} else {firstPosition = firstGlyphPosition.memory}// 4.我们要开始画线了CGContextBeginPath(context)// 5.获取定义的线的颜色, 默认为黑色let lineColor = attributes[NSStrikethroughColorAttributeName]if nil == lineColor {CGContextSetStrokeColorWithColor(context, UIColor.blackColor().CGColor)} else {CGContextSetStrokeColorWithColor(context, (lineColor as! UIColor).CGColor)}// 6.字体高度, 中间位置为x高度的一半let font = attributes[NSFontAttributeName] ?? UIFont.systemFontOfSize(UIFont.systemFontSize())var strikeHeight: CGFloat = font.xHeight / 2.0 + firstPosition.y// 多行调整let pt = CGContextGetTextPosition(context)strikeHeight += pt.y// 画线的宽度let typographicWidth = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), nil, nil, nil))// 7.开始画线CGContextMoveToPoint(context, pt.x + firstPosition.x, strikeHeight)CGContextAddLineToPoint(context, pt.x + firstPosition.x + typographicWidth, strikeHeight)CGContextStrokePath(context)
}
- 然后在controller中给attributedText添加删除线样式
// 删除线
let strikethroughStyle = [NSStrikethroughStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue,NSStrikethroughColorAttributeName: UIColor.cyanColor()]
attributedText.addAttributes(strikethroughStyle, range: NSMakeRange(150, 20))
这样就实现了删除线样式效果了

至此, 删除线的样式就完成了, 其他样式将可能在下一篇CoreText文章中实现。
源码在此, 请参考源码中的CoreText/1文件夹!!!
参考:
CoreText基础概念
CoreText入门
Nimbus
本文由啸寒原创, 转载请注明出处!!!
相关文章:

mysql连接hang住问题分析
【问题现象】: 1. Linuxc多线程连接mysql数据库,每次都是短连接,操作完后就释放连接,有时候会出现mysql_real_connect挂住的现象 2. 挂住超时mysql_real_connect返回后报错如下:Lostconnection to MySQL s…

【Linux学习笔记】 -- 基本Shell命令
常见的目录名均基于文件系统层级标准(filesystem hierarchy standard,FHS) Linux的四个部分: 1 Linux内核:控制所有硬软件,必要时分配硬件根据需要执行软件 系统内存管理:可用物理内存 创建、管理虚拟内存[交换空间…

【OpenCV】图像代数运算:平均值去噪,减去背景
代数运算,就是对两幅图像的点之间进行加、减、乘、除的运算。四种运算相应的公式为: 代数运算中比较常用的是图像相加和相减。图像相加常用来求平均值去除addtive噪声或者实现二次曝光(double-exposure)。图像相减用于减去背景或周…

简明 Vim 练级攻略(转)
vim的学习曲线相当的大(参看各种文本编辑器的学习曲线),所以,如果你一开始看到的是一大堆VIM的命令分类,你一定会对这个编辑器失去兴趣的。下面的文章翻译自《Learn Vim Progressively》,我觉得这是给新手最…
iOS 的离屏渲染
原文链接:http://www.imlifengfeng.com/blog/?p593OpenGL ES 是一套多功能开放标准的用于嵌入系统的 C-based 的图形库,用于 2D 和 3D 数据的可视化。OpenGL 被设计用来转换一组图形调用功能到底层图形硬件(GPU),由 G…

MySQL 常见操作指令
什么是SQL? SQL(Structured Query Language)用于访问和操作数据库的结构化查询语言。 数据库包含一个或多个表,每个表均有名称标识,包含数据的记录(行)。 典型的SQL语句 1. SELEC语句 SELE…
iOS 实现点击微信头像效果
来源:伯乐在线 - 小良 如有好文章投稿,请点击 → 这里了解详情 如需转载,发送「转载」二字查看说明 公司产品需要实现点击个人主页头像可以放大头像、缩放头像、保存头像效果(和点击微信个人头像类似),故找…

HDU 4292 Food(dinic +拆点)
题目链接 我做的伤心了,不知是模版效率低,还是错了,交上就是TLE,找了份别人的代码,改了好几下终于过了。。 1 #include <cstdio>2 #include <cstring>3 #include <queue>4 #include <map>5 #i…

jQuery中用ajax访问php接口文件
js代码 function ajax_request(){var result;var articleId new Object();articleIdgetArticleId();$.ajax({url: "/topicPage/getComment.php",//请求php文件的路径data:{id:articleId},//请求中要传送的参数,会自动拼接成一个路径,在php中用get方式获取…

Python 数据库操作 psycopg2
文章目录安装基本使用安装 psycopg 是 Python 语言中 PostpreSQL数据库接口 安装环境: Python:v2.7, v3.4~3.8PostGreSQL:7.4~12 pip install psycopg2基本使用 import psycopg2def connect_db(host: str,port: int,database: str,user:…

Android logcat命令详解
一、logcat命令介绍 1.android log系统 2.logcat介绍 logcat是android中的一个命令行工具,可以用于得到程序的log信息 log类是一个日志类,可以在代码中使用logcat打印出消息 常见的日志纪录方法包括:方法 描述 v(String,String) (vervbose)显…

[iOS]如何重新架构 JPVideoPlayer ?
注意:此文为配合 JPVideoPlayer version 2.0 版本发布而写,如果你想了解 2.0 版本的更新内容和所有实现细节,请点击前往 GitHub。 导言:我几个月前写了一个在 UITableView 中滑动 UITableViewCell 播放视频的框架,类似…

函数项目一个超感人的故事:关于swfupload在某些环境下面session丢失的完美解决方案(看完我哭了)...
查了好多资料,发现还是不全,干脆自己整理吧,至少保证在我的做法正确的,以免误导读者,也是给自己做个记录吧! 标题吸引到你了吗? 先说一下这个题问成形的原因。大家都晓得 session是靠cookie中的…

【学习笔记】git 使用文档
安装 git # mac 环境 brew install git检查是否安装成功 ➜ ~ git --version git version 2.20.1 (Apple Git-117)卸载 git ➜ ~ which -a git /usr/bin/git ➜ ~ cd /usr/bin ➜ bin sudo rm -rf git*git init 命令 对一个空文件,git 初始化。文件名称增加…

UIBezierPath和CAShapeLayer创建不规则View(Swift 3.0)
最近一个朋友在做图片处理的 App,想要实现类似 MOLDIV App 拼图的UI效果(如何创建不规则的 view),就问我有什么想法。我首先想到的就是 UIBezierPathCAShapeLayer的方式,为了验证自己的想法,写了一个小 dem…

http响应状态
Servlet API: javax.servlet.http.HttpServletResponse 用于创建HTTP响应,包括HTTP协议的状态行、响应头以及消息体 HTTP状态码: 100-199:表示信息性代码,标示客户端应该采取的其他动作,请求正在进行。 200…

antlr.collections.AST.getLine()I问题的起因及解决
在我们的java web 项目中引入hibernate和struts,当我们使用HQL语句进行查询时会报 antlr.collections.AST.getLine()I的错误,导致程序无法继续运行,这并不是我们的程序写的有错误,出现这个异常的原因是因为我们使用的hibernate和s…

2018湖湘杯海选复赛Writeup
2018湖湘杯Writeup0x01 签到题0x02 MISC Flow0x03 WEB Code Check0x04 WEB Readflag0x05 WEB XmeO0x06 Reverse Replace0x07 MISC Disk0x08 Crypto Common Crypto0x09 Reverse HighwayHash640x10 Web Mynot0x01 签到题 关注合天智汇公众号,回复hxb2018得到flag。0x…

Operation Queues并发编程
并发、异步在我们的编程中,见到的太多了。在iOS中,实现并发的主要三个途径Operation Queues、Dispatch Queues、Dispatch Sources,今天我们就来详细介绍Operatin Queues的使用,花了两天时间写这一篇,值得一看。 为什么…

socket 服务器浏览器与服务器客户端实例
一、服务器与浏览器 // 取得本机的loopback网络地址,即127.0.0.1 IPAddress address IPAddress.Loopback; IPEndPoint endPoint new IPEndPoint(address, 49152); Socket socket new Socket(AddressFamily.InterNetwork, Socke…

匹配3位或4位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔...
public bool IsPhone(string input){string pattern "^\\(0\\d{2}\\)[- ]?\\d{8}$|^0\\d{2}[- ]?\\d{8}$|^\\(0\\d{3}\\)[- ]?\\d{7}$|^0\\d{3}[- ]?\\d{7}$";Regex regex new Regex(pattern);return regex.IsMatch(input);} 转载于:https://www.cnblogs.com/…

Mac MySQL配置环境变量的两种方法
第一种: 1.打开终端,输入: cd ~ 会进入~文件夹 2.然后输入:touch .bash_profile 回车执行后, 3.再输入:open -e .bash_profile 会在TextEdit中打开这个文件(如果以前没有配置过环境变量,那么这…

linux之x86裁剪移植---字符界面sdl开发入门
linux下有没有TurboC2.0那样的画点、线、圆的图形函数库,有没有grapihcs.h,或者与之相对应或相似的函数库是什么?有没有DirectX这样的游戏开发库?SDL就是其中之一。SDL(Simple DirectMedia Layer)是一个夸平…

iOS 视频捕获系列Swift之AVFoundation(一)
iOS 视频捕获系列之AVFoundation(一) AVCaptureMovieFileOutput系列 在iOS开发过程中,或多或少的都涉及视频的操作。 尤其在去年直播行业的带动下,移动端对视频的处理也愈来愈发要求严格。 本文也是在 这篇 中参考而来。 Swift 版本哦! 本文 …

C#做外挂常用API
using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; //这个肯定要的 namespace WindowsApplication1 {class win32API{public const int OPEN_PROCESS_ALL 2035711;public const int PAGE_READWRITE 4;public con…

phpinfo 信息利用
0x01 基础信息 1.system info:提供详细的操作系统信息,为提权做准备。 2.extension_dir:php扩展的路径 3.$_SERVER[‘HTTP_HOST’]:网站真实IP、CDN什么的都不存在的,找到真实ip,扫一扫旁站,没准就拿下几个站。 4.$_SERVER[‘…

iOS三种录制视频方式详细对比
先附上参考资料 http://www.jianshu.com/p/16cb14f53933 https://developer.apple.com/library/content/samplecode/AVSimpleEditoriOS/Introduction/Intro.html https://github.com/objcio/VideoCaptureDemo https://github.com/gsixxxx/DTSmallVideo https://github.com/Andy…

C# 实现Oracle中的数据与Excel之间的转换
最近项目要求实现数据库之间数据在各个数据库之间导入导出,在此做个笔记 1. 将Oracle中的表导入到Excel中,反之亦然 private static readonly string connectionString ConfigurationManager.ConnectionStrings["OracleConnection"].Connecti…

【转】Word2007中不连续页码设置 多种页码设置
【转】Word2007中不连续页码设置 多种页码设置 页码是论文必不可少的部分。我们看一下如何添加页码,并且针对一些特殊的格式要求怎么应对: 如果是【毕业论文】有多种混合页码,有Ⅰ、Ⅱ、Ⅲ。。。还有1、2、3 。。。请直接看【第二种方法】。 …

vim编辑器异常退出产生备份文件
当非正常关闭vim编辑器时(比如直接关闭终端或者电脑断电),会生成一个.swp文件,这个文件是一个临时交换文件,用来备份缓冲区中的内容。 需要注意的是如果你并没有对文件进行修改,而只是读取文件,…