必威-必威-欢迎您

必威,必威官网企业自成立以来,以策略先行,经营致胜,管理为本的商,业推广理念,一步一个脚印发展成为同类企业中经营范围最广,在行业内颇具影响力的企业。

链接识别并替换成,必威2.文字的样式包括很多

2019-09-12 19:42 来源:未知

二. 使用基本步骤

新建一个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的背景色设置为白色, 那么效果就应该如下了

必威 1

文字绘制出来了, 这就是CoreText使用最基本的步骤了。

图文混排

CoreText如果只能定义文本绘制区域,那就太没劲了,CoreText还可以支持图文混排,本地图片和网络图片。

CoreText从绘制纯文本到绘制图片,依然是使用NSAttributedString,只不过图片的实现方式是用一个空白字符作为在NSAttributedString中的占位符,然后设置代理,告诉CoreText给该占位字符留出一定的宽高。最后把图片绘制到预留的位置上。

必威 2配图

图中 第一个图片是存在本地,第二个图片是来自网络。下面看看怎么做。代码有点长 但是思路应该清晰。

import UIKitclass CTPicTxtView: UIView { var image:UIImage? override func drawRect(rect: CGRect) { super.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 = UIBezierPath(rect: rect) // 4 创建需要绘制的文字 let attrString = "Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!" let mutableAttrStr = NSMutableAttributedString(string: attrString) mutableAttrStr.addAttributes([NSFontAttributeName:UIFont.systemFontOfSize, NSForegroundColorAttributeName:UIColor.redColor() ], range: NSMakeRange mutableAttrStr.addAttributes([NSFontAttributeName:UIFont.systemFontOfSize,NSUnderlineStyleAttributeName: 1 ], range: NSMakeRange let style = NSMutableParagraphStyle() //用来设置段落样式 style.lineSpacing = 6 //行间距 mutableAttrStr.addAttributes([NSParagraphStyleAttributeName:style], range: NSMakeRange(0, mutableAttrStr.length)) // 5 为图片设置CTRunDelegate,delegate决定留给图片的空间大小 var imageName = "mc" var imageCallback = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: {  -> Void in }, getAscent: {  -> CGFloat in // let imageName = "mc"// refCon.initialize()// let image = UIImage(named: imageName) return 100 //返回高度 }, getDescent: {  -> CGFloat in return 50 //返回底部距离 }) {  -> CGFloat in // let imageName = String// let image = UIImage(named: imageName) return 100 //返回宽度 } let runDelegate = CTRunDelegateCreate(&imageCallback, &imageName) let imgString = NSMutableAttributedString(string: " ") // 空格用于给图片留位置 imgString.addAttribute(kCTRunDelegateAttributeName as String, value: runDelegate!, range: NSMakeRange //rundelegate 占一个位置 imgString.addAttribute("imageName", value: imageName, range: NSMakeRange//添加属性,在CTRun中可以识别出这个字符是图片 mutableAttrStr.insertAttributedString(imgString, atIndex: 15) //网络图片相关 var imageCallback1 = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: {  -> Void in }, getAscent: {  -> CGFloat in return 70 //返回高度 }, getDescent: {  -> CGFloat in return 50 //返回底部距离 }) {  -> CGFloat in return 100 //返回宽度 } var imageUrl = "http://img3.3lian.com/2013/c2/64/d/65.jpg" //网络图片链接 let urlRunDelegate = CTRunDelegateCreate(&imageCallback1, &imageUrl) let imgUrlString = NSMutableAttributedString(string: " ") // 空格用于给图片留位置 imgUrlString.addAttribute(kCTRunDelegateAttributeName as String, value: urlRunDelegate!, range: NSMakeRange //rundelegate 占一个位置 imgUrlString.addAttribute("urlImageName", value: imageUrl, range: NSMakeRange//添加属性,在CTRun中可以识别出这个字符是图片 mutableAttrStr.insertAttributedString(imgUrlString, atIndex: 50) // 6 生成framesetter let framesetter = CTFramesetterCreateWithAttributedString(mutableAttrStr) let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, mutableAttrStr.length), path.CGPath, nil) // 7 绘制除图片以外的部分 CTFrameDraw(frame,context!) // 8 处理绘制图片逻辑 let lines = CTFrameGetLines as NSArray //存取frame中的ctlines let ctLinesArray = lines as Array var originsArray = [CGPoint](count:ctLinesArray.count, repeatedValue: CGPointZero) let range: CFRange = CFRangeMake CTFrameGetLineOrigins(frame,range,&originsArray) //遍历CTRun找出图片所在的CTRun并进行绘制,每一行可能有多个 for i in 0..<lines.count{ //遍历每一行CTLine let line = lines[i] var lineAscent = CGFloat() var lineDescent = CGFloat() var lineLeading = CGFloat() //该函数除了会设置好ascent,descent,leading之外,还会返回这行的宽度 CTLineGetTypographicBounds(line as! CTLineRef, &lineAscent, &lineDescent, &lineLeading) let runs = CTLineGetGlyphRuns(line as! CTLine) as NSArray for j in 0..<runs.count{ // 遍历每一个CTRun var runAscent = CGFloat() var runDescent = CGFloat() let lineOrigin = originsArray[i]// 获取该行的初始坐标 let run = runs[j] // 获取当前的CTRun let attributes = CTRunGetAttributes(run as! CTRun) as NSDictionary let width = CGFloat( CTRunGetTypographicBounds(run as! CTRun, CFRangeMake, &runAscent, &runDescent, nil)) let runRect = CGRectMake(lineOrigin.x + CTLineGetOffsetForStringIndex(line as! CTLine, CTRunGetStringRange(run as! CTRun).location, nil), lineOrigin.y - runDescent, width, runAscent + runDescent) let imageNames = attributes.objectForKey("imageName") let urlImageName = attributes.objectForKey("urlImageName") if imageNames is NSString { //本地图片 let image = UIImage(named: imageName as String) let imageDrawRect = CGRectMake(runRect.origin.x, lineOrigin.y-runDescent, 100, 100) CGContextDrawImage(context, imageDrawRect, image?.CGImage) } if let urlImageName = urlImageName as? String{ var image:UIImage? let imageDrawRect = CGRectMake(runRect.origin.x, lineOrigin.y-runDescent, 100, 100) if self.image == nil{ image = UIImage(named:"hs") //灰色图片占位 //去下载 if let url = NSURL(string: urlImageName){ let request = NSURLRequest NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data, resp, err) -> Void in if let data = data{ dispatch_sync(dispatch_get_main_queue -> Void in self.image = UIImage(data: data) self.setNeedsDisplay() //下载完成会重绘 }) } }).resume() } }else{ image = self.image } CGContextDrawImage(context, imageDrawRect, image?.CGImage) } } } } }

1、2、3、4和前面简单Demo是一模一样的 ,只是加了个行间距。

5、 这块用一个回调设置了图片大小等信息 ,然会创建了一个CTRun的代理,创建一个空白占位字符,给它加了个属性,后面绘制的时候好识别。 最后把这个占位符加到我们属性文本的某个位置。

地下网络图片 name换成了url 其他如法炮制

6、7和上小结一样的

8、 正式开始处理图片部分

根据CTFrame 获取 CTLine 获取 originsArray 每一行的原点,用来定位 。根据CTLine获取到 CTRun 。 CTRun是每一个相同属性字符串 ,但是不会隔行。

遍历CTRun 根据我们前面设置的属性 找到本地图片进行绘制

网络图片的绘制也很简单如果没有下载 先放个灰色的图占位,然后去下载,下载好了 赋值给self的一个变量 ,然后重绘就OK了 关于NSURLSession不会使用的可以看我的另一篇文章。NSURLSession

关于CoreText还有很多,逐行排版 文字和emoji 混排问题 , 连接识别 ,点击图片 点击连接等等。。篇幅有点长。。下一篇接着介绍,这篇先到这边。

本文实例代码已上传github:

//获取画出来的内容的行数

|

NSAttributedString

看yykit的demo里,微博的页面的富文本使用了NSAttributedString,这里记录下学习笔记。
这里要把"我家这个好忠犬啊~[喵喵] http://t.cn/Ry4UXdF //@我是呆毛芳子蜀黍w:这是什么鬼?[喵喵] //@清新可口喵酱圆脸星人是扭蛋狂魔:窝家这个超委婉的拒绝了窝"在手机上显示成;

![Uploading 屏幕快照 2017-08-17 上午11.11.20_514440.png . . .]

@用户名用到了正则匹配,可以得到一个nsrange的数组,是@用户名的nsrange;
表情也是用到了正则匹配,得到每个表情的nsrange,从本地寻找表情对应的图片,然后用到了NSTextAttachment来生成NSAttributedString,然后把表情进行了替换。
http://t.cn/Ry4UXdF这个链接被替换成了图片和文字,图片是从网络上下载的。可以先判断本地是否有图片的缓存,如果没有,先用占位图生成NSTextAttachment,先显示占位图,等图片下载完以后就重新替换掉图片。
demo

CoreText是Mac OS和iOS系统中处理文本的low-level API, 不管是使用OC还是swift, 实际我们使用CoreText都还是间接或直接使用C语言在写代码。CoreText是iOS和Mac OS中文本处理的根基, TextKit和WebKit都是构建于其上。

我们来看一个CoreText对象模型图

以下将介绍如何实现类型的文本点击事件响应:

CoreText会把一行里连在一起相同属性的文字合在一起作为一个CTRun,每一行是一个CTLine,多行合在一起组成CTFrame。如上图,第一行的文字有两种样式,第一部分是加粗,第二部分是斜体,因为样式不同所以分成了两个CTRun,CTLine包含了这两个CTRun,CTFrame包含了所有CTLine。

字形度量

字形度量就是字形的各个参数:

必威 3

ios_coretext_glyphs_1.jpg

必威 4

glyph_metrics_2x.png

  • bounding box(边界框),这是一个假想的框子,它尽可能紧密的装入字形。

  • baseline(基线),一条假想的线,一行上的字形都以此线作为上下位置的参考,在这条线的左侧存在一个点叫做基线的原点。

  • ascent(上行高度),从原点到字体中最高(这里的高深都是以基线为参照线的)的字形的顶部的距离,ascent是一个正值。

  • descent(下行高度),从原点到字体中最深的字形底部的距离,descent是一个负值(比如一个字体原点到最深的字形的底部的距离为2,那么descent就为-2)。

  • linegap(行距),linegap也可以称作leading(其实准确点讲应该叫做External leading)。

  • leading,文档说的很含糊,其实是上一行字符的descent到- 下一行的ascent之间的距离。

  • 所以字体的高度是由三部分组成的:leading + ascent + descent。

字形和字符,一些Metrics专业知识还可以参考Free Type的文档 Glyph metrics,其实iOS就是使用Free Type库来进行字体渲染的。苹果文档 Querying Font Metrics ,Text Layout。

一. 基础

1.在使用CoreText编写代码之前, 需要先了解一些基础知识。下图是CoreText的基础框架

必威 5

  • CTFrame可以想象成画布, 画布的大小范围由CGPath决定
  • CTFrame由很多CTLine组成, CTLine表示为一行
  • CTLine由多个CTRun组成, CTRun相当于一行中的多个块, 但是CTRun不需要你自己创建, 由NSAttributedString的属性决定, 系统自动生成。每个CTRun对应不同属性
  • CTFramesetter是一个工厂, 创建CTFrame, 一个界面上可以有多个CTFrame

2.文字的样式包括很多, 而每个字符的显示要归功于字体, 而字体包括很多基础知识, 比如磅值, 样式, 基线, 连字等等, 这里就不做更多介绍, 推荐两篇文章阅读。CoreText基础概念CoreText入门

在这里, 贴出CoreText基础概念文中关于字体结构的图(图片版权归此文作者)

必威 6必威 7

来一段枯燥的讲解

-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event

  获取上下文 也就是获取画布

  CGContextRef context = UIGraphicsGetCurrentContext();

CoreText


Core Text是和Core Graphics配合使用的,一般是在UIView的drawRect方法中的Graphics Context上进行绘制的。Core Text真正负责绘制的是文本部分,如果要绘制图片,可以使用CoreText给图片预留出位置,然后用Core Graphics绘制。demo地址

三.简单的富文本Label

上面简单的绘制步骤中, 最后一步绘制frame, 是将整个frame当做一块绘制, 至于什么换行, 行中的样式什么的都是系统自己决定了。在开始之前, 我们将这个绘制frame改成我们自己一行一行, 甚至一个run一个run的绘制

  • 按行绘制
// 1.获得CTLine数组let lines = CTFrameGetLines // 2.获得行数let numberOfLines = CFArrayGetCount // 3.获得每一行的origin, CoreText的origin是在字形的baseLine处的, 请参考字形图var lineOrigins = [CGPoint](count: numberOfLines, repeatedValue: CGPointZero)CTFrameGetLineOrigins(frame, CFRangeMake, &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 as Array runs.forEach { run in CTRunDraw(run as! CTRun, context, CFRangeMake } }}

用此函数替换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 // 下划线let underlineStyles = [NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue, NSUnderlineColorAttributeName: UIColor.orangeColor()]attributedText.addAttributes(underlineStyles, range: NSMakeRange // 字体attributedText.addAttribute(NSFontAttributeName, value: UIFont.boldSystemFontOfSize, range: NSMakeRange // 描边:组成字符的线或曲线。可以加粗或改变字符形状let strokeStyles = [NSStrokeWidthAttributeName: 10, NSStrokeColorAttributeName: UIColor.blueColor()]attributedText.addAttributes(strokeStyles, range: NSMakeRange // 横竖文本attributedText.addAttribute(NSVerticalGlyphFormAttributeName, value: 0, range: NSMakeRange // 字符间隔attributedText.addAttribute(NSKernAttributeName, value: 5, range: NSMakeRange// 段落样式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))

此时, 你就会看到如下效果

必威 8

  • 效果出来了, 你是否就满足了。那其他平常可以使用的样式要怎么样使用CoreText来实现了? 我们就先实现一个样式--删除线, 将上面的drawLine函数改成如下
// 画一行func drawLine(line: CTLine, context: CGContext) { let runs = CTLineGetGlyphRuns as Array runs.forEach { run in CTRunDraw(run as! CTRun, context, CFRangeMake // 获得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 = .StyleNone CFNumberGetValue(styleRef as! CFNumber, CFNumberType.SInt64Type, &style) // 如果定义为none, 就不用画了 guard style != .StyleNone else { return } // 2.获得画线的宽度 var lineWidth: CGFloat = 1 if (style.rawValue & NSUnderlineStyle.StyleThick.rawValue) == NSUnderlineStyle.StyleThick.rawValue { lineWidth *= 2 } CGContextSetLineWidth(context, lineWidth) // 3.获取画线的起点 var firstPosition = CGPointZero let firstGlyphPosition = CTRunGetPositionsPtr if nil == firstGlyphPosition { let positions = UnsafeMutablePointer<CGPoint>.alloc positions.initialize(CGPointZero) CTRunGetPositions(run, CFRangeMake, positions) firstPosition = positions.memory positions.destroy() } else { firstPosition = firstGlyphPosition.memory } // 4.我们要开始画线了 CGContextBeginPath // 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 strikeHeight += pt.y // 画线的宽度 let typographicWidth = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake, nil, nil, nil)) // 7.开始画线 CGContextMoveToPoint(context, pt.x + firstPosition.x, strikeHeight) CGContextAddLineToPoint(context, pt.x + firstPosition.x + typographicWidth, strikeHeight) CGContextStrokePath}
  • 然后在controller中给attributedText添加删除线样式
// 删除线let strikethroughStyle = [NSStrikethroughStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue, NSStrikethroughColorAttributeName: UIColor.cyanColor()]attributedText.addAttributes(strikethroughStyle, range: NSMakeRange

这样就实现了删除线样式效果了

必威 9

至此, 删除线的样式就完成了, 其他样式将可能在下一篇CoreText文章中实现。源码在此, 请参考源码中的CoreText/1文件夹!!!

参考:CoreText基础概念CoreText入门Nimbus

本文由啸寒原创, 转载请注明出处!!!

必威 10配图

//获取每个CTRun

|

代码

自定义一个继承自UIView的子类CoreTextView;在.m文件里引入头文件CoreText/CoreText.h重写drawRect方法:

void RunDelegateDeallocCallback( void* refCon ){

}

CGFloat RunDelegateGetAscentCallback( void *refCon ){
    NSString *imageName = (__bridge NSString *)refCon;
    CGFloat height = [UIImage imageNamed:imageName].size.height;
    return height;
}

CGFloat RunDelegateGetDescentCallback(void *refCon){
    return 0;
}

CGFloat RunDelegateGetWidthCallback(void *refCon){
    NSString *imageName = (__bridge NSString *)refCon;
    CGFloat width = [UIImage imageNamed:imageName].size.width;
    return width;
}



- (void)drawRect:(CGRect)rect{
    [super drawRect:rect];
    //得到当前绘制画布的上下文,用于将后续内容绘制在画布上
    CGContextRef context = UIGraphicsGetCurrentContext();

    //将坐标系上下翻转。对于底层的绘制引擎来说,屏幕的左下角是坐标原点(0,0),而对于上层的UIKit来说,屏幕的左上角是坐标原点,为了之后的坐标系按UIKit来做,在这里做了坐标系的上下翻转,这样底层和上层的(0,0)坐标就是重合的了
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0,-1.0);

    //创建绘制的区域,这里将UIView的bounds作为绘制区域
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, self.bounds);

    NSMutableAttributedString * attString = [[NSMutableAttributedString alloc] initWithString:@"海洋生物学家在太平洋里发现了一条与众不同的鲸。一般蓝鲸的“歌唱”频率在十五到二十五赫兹,长须鲸子啊二十赫兹左右,而它的频率在五十二赫兹左右。"];
    //设置字体
    [attString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:24] range:NSMakeRange(0, 5)];
    [attString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:13] range:NSMakeRange(6, 2)];
    [attString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:38] range:NSMakeRange(8, attString.length - 8)];


    //设置文字颜色
    [attString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, 11)];
    [attString addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(11, attString.length - 11)];


    NSString * imageName = @"jingyu";
    CTRunDelegateCallbacks callbacks;
    callbacks.version = kCTRunDelegateVersion1;
    callbacks.dealloc = RunDelegateDeallocCallback;
    callbacks.getAscent = RunDelegateGetAscentCallback;
    callbacks.getDescent = RunDelegateGetDescentCallback;
    callbacks.getWidth = RunDelegateGetWidthCallback;

    CTRunDelegateRef runDelegate = CTRunDelegateCreate(&callbacks, (__bridge void * _Nullable)(imageName));
    //空格用于给图片留位置
    NSMutableAttributedString *imageAttributedString = [[NSMutableAttributedString alloc] initWithString:@" "];
     CFAttributedStringSetAttribute((CFMutableAttributedStringRef)imageAttributedString, CFRangeMake(0, 1), kCTRunDelegateAttributeName, runDelegate);
    CFRelease(runDelegate);
    [imageAttributedString addAttribute:@"imageName" value:imageName range:NSMakeRange(0, 1)];
    [attString insertAttributedString:imageAttributedString atIndex:1];
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attString.length), path, NULL);
    //把frame绘制到context里
    CTFrameDraw(frame, context);

    NSArray * lines = (NSArray *)CTFrameGetLines(frame);
    NSInteger lineCount = lines.count;
    CGPoint lineOrigins[lineCount];
    //拷贝frame的line的原点到数组lineOrigins里,如果第二个参数里的length是0,将会从开始的下标拷贝到最后一个line的原点
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), lineOrigins);

    for (int i = 0; i < lineCount; i++) {
        CTLineRef line = (__bridge CTLineRef)lines[i];
        NSArray * runs = (__bridge NSArray *)CTLineGetGlyphRuns(line);
        for (int j = 0; j < runs.count; j++) {
            CTRunRef run =  (__bridge CTRunRef)runs[j];
            NSDictionary * dic = (NSDictionary *)CTRunGetAttributes(run);
            CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[dic objectForKey:(NSString *)kCTRunDelegateAttributeName];
            if (delegate == nil) {
                continue;
            }
            NSString * imageName = [dic objectForKey:@"imageName"];
            UIImage * image = [UIImage imageNamed:imageName];
            CGRect runBounds;
            CGFloat ascent;
            CGFloat descent;
            runBounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
            runBounds.size.height = ascent + descent;
            CFIndex index = CTRunGetStringRange(run).location;
            CGFloat xOffset = CTLineGetOffsetForStringIndex(line, index, NULL);
            runBounds.origin.x = lineOrigins[i].x + xOffset;
            runBounds.origin.y = lineOrigins[i].y;
            runBounds.size =image.size;
            CGContextDrawImage(context, runBounds, image.CGImage);
        }
    }
    //底层的Core Foundation对象由于不在ARC的管理下,需要自己维护这些对象的引用计数,最后要释放掉。
    CFRelease(frame);
    CFRelease(path);
}

运行后效果如下:

必威 11

Simulator Screen Shot 2016年8月27日 上午10.22.23.png

效果

//给文本赋值

   创建CTFrame,通过coreText提供的一个API

CoreText对象模型

必威 12

65cc0af7gw1e2uxd1gmhwj.jpg

必威 13

65cc0af7gw1e2uyn6r88oj.jpg

](http://upload-images.jianshu.io/upload_images/1311714-d063f8012ddf2f36.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
从图中可以看到,我们首先通过CFAttributeString来创建CTFramaeSetter,然后再通过CTFrameSetter来创建CTFrame。
在CTFrame内部,是由多个CTLine来组成的,每个CTLine代表一行,每个CTLine是由多个CTRun来组成,每个CTRun代表一组显示风格一致的文本。

  • CTFrameSetter CTFrameSetter是通过CFAttributeString进行初始化它负责根据path生产对应的CTFrame;
  • CTFrame CTFrame可以通过CTFrameDraw函数直接绘制到context上,我们可以在绘制之前,操作CTFrame中的CTline,进行一些参数的微调;
  • CTLine CTLine可以看做Core Text绘制中的一行的对象,通过它可以获得当前行的line ascent, line descent, line heading,还可以获得CTLine下的所有CTRun;
  • CTRun CTRun是一组共享相同attributes的集合体;
    要绘制图片,需要用CoreText的CTRun为图片在绘制过程中留出空间。这个设置要用到CTRunDelegate。我们可以在要显示图片的地方,用一个特殊的空白字符代替,用CTRunDelegate为其设置ascent,descent,width等参数,这样在绘制文本的时候就会把图片的位置留出来,用CGContextDrawImage方法直接绘制出来就行了。

创建CTRunDelegate:

CTRunDelegateRef __nullable CTRunDelegateCreate(
    const CTRunDelegateCallbacks* callbacks,
    void * __nullable refCon ) 

创建CTRunDelegate需要两个参数,一个是callbacks结构体,还有一个是callbacks里的函数调用时需要传入的参数。

typedef struct
{
    CFIndex                         version;
    CTRunDelegateDeallocateCallback dealloc;
    CTRunDelegateGetAscentCallback  getAscent;
    CTRunDelegateGetDescentCallback getDescent;
    CTRunDelegateGetWidthCallback   getWidth;
} CTRunDelegateCallbacks;

callbacks是一个结构体,主要包含了返回当前CTRun的ascent,descent和width函数。

一个简单的例子

首先新建一个CTView继承自UIView

import UIKitclass CTView: UIView { override func drawRect(rect: CGRect) { super.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 = CGPathCreateMutable() CGPathAddRect(path, nil, self.bounds) // 4 let attrString = NSAttributedString(string:"Hello CoreText!") let framesetter = CTFramesetterCreateWithAttributedString(attrString) let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrString.length), path, nil) // 5 CTFrameDraw(frame,context!) }}

然后把这个view加在ViewController上

let ctView = CTView()ctView.frame = CGRectMake(10, 150, self.view.bounds.width - 20, 200)ctView.backgroundColor = UIColor.whiteColor()self.view.addSubview

运行效果:

必威 14配图

解释:1、 通过UIGraphisGetCurrentContext()获取当前的环境2、将坐标系上下翻转。对于底层的绘制引擎来说,屏幕的左下角是坐标。而对于上层的 UIKit 来说,左上角是 坐标。所以我们为了之后的坐标系描述按 UIKit 来做,所以先在这里做一个坐标系的上下翻转操作。翻转之后,底层和上层的 坐标就是重合的了。3、创建绘制区域CGPathCreateMutable(),CoreText 本身支持各种文字排版的区域,我们这里简单地将 UIView 的整个界面作为排版的区域。

当然这里如果觉得CGMutablePath 不好用 可以选择使用更方便的UIBezierPath来操作排版区域.

把上文中的3改成

let path1 = UIBezierPath(roundedRect: self.bounds, cornerRadius:self.bounds.size.width/2 )

把4改成 顺便给文字加了点属性

 // 4let attrString = "Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!" let mutableAttrStr = NSMutableAttributedString(string: attrString)mutableAttrStr.addAttributes([NSFontAttributeName:UIFont.systemFontOfSize, NSForegroundColorAttributeName:UIColor.redColor() ], range: NSMakeRangemutableAttrStr.addAttributes([NSFontAttributeName:UIFont.systemFontOfSize,NSUnderlineStyleAttributeName: 1 ], range: NSMakeRangelet framesetter = CTFramesetterCreateWithAttributedString(mutableAttrStr)let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, mutableAttrStr.length), path1.CGPath, nil)

排版区域就变了 而且使用了UIBezierPath的API

必威 15配图

4、根据AttributedString生成CTFramesetterRef,根据framesetter和绘图区域创建CTFrame

5、使用CTFrameDraw进行绘制(后面复杂的可能不会直接画frame 而是选择一行一行的画 )

}

  • (CFIndex)touchContentOffsetInView:(UIView *)view atPoint:(CGPoint)point data:(HTLSearchOptimizeCoreTextData *)data {

    CTFrameRef textFrame = data.ctFrame;

    CFArrayRef lines = CTFrameGetLines(textFrame);

    if (!lines) {

      return -1;
    

    }

    CFIndex count = CFArrayGetCount(lines);

    // 获得每一行的origin坐标

    CGPoint origins[count];

    CTFrameGetLineOrigins(textFrame, CFRangeMake(0,0), origins);

    // 翻转坐标系

    CGAffineTransform transform = CGAffineTransformMakeTranslation(0, view.bounds.size.height);

    transform = CGAffineTransformScale(transform, 1.f, -1.f);

    CFIndex idx = -1;

    for (int i = 0; i < count; i++) {

      CGPoint linePoint = origins[i];
    
      CTLineRef line = CFArrayGetValueAtIndex(lines, i);
    
      // 获得每一行的CGRect信息
    
      CGRect flippedRect = [self getLineBounds:line point:linePoint];
    
      CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);
    
      if (CGRectContainsPoint(rect, point)) {
    
          // 将点击的坐标转换成相对于当前行的坐标
    
          CGPoint relativePoint = CGPointMake(point.x-CGRectGetMinX(rect),
    
                                              point.y-CGRectGetMinY(rect));
    
          // 获得当前点击坐标对应的字符串偏移
    
          idx = CTLineGetStringIndexForPosition(line, relativePoint);
    
      }
    

    }

    return idx;

效果

lblSupport.userInteractionEnabled=YES;

Quartz 能够直接处理字体(font)和字形(glyphs),将文字渲染到界面上,它是基础库中唯一能够处理字形的模块。因此,CoreText 为了排版,需要将显示的文本内容、位置、字体、字形直接传递给 Quartz。相比其它 UI 组件,由于 CoreText 直接和 Quartz 来交互,所以它具有高速的排版效果。

let str = "这是一段用来测试的字符串 this is a string for test"let dic = [NSFontAttributeName:UIFont.boldSystemFontOfSize, NSForegroundColorAttributeName:UIColor.redColor()]let attrStr = NSAttributedString(string: str, attributes: dic)label.attributedText = attrStr

*  @param msg      昵称

CoreText几个比较重要的概念

如果你啥也不做处理,直接在这个context上进行CoreText绘制,你会发现文字是镜像且上下颠倒。

{

image.png

必威 16配图

3.3 实现初始化方法:

CTFrameRef ctFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributed.length), path, NULL);

  • NSFontAttributeName 设置字体属性,默认值:字体:Helvetica 字号12
  • NSForegroundColorAttributeName 设置字体颜色,取值为 UIColor对象,默认值为
  • NSBackgroundColorAttributeName 设置字体所在区域背景颜色,取值为 UIColor对象,默认值为nil, 透明
  • NSLigatureAttributeName 设置连体属性,取值为NSNumber 对象,0 表示没有连体字符,1 表示使用默认的连体字符
  • NSKernAttributeName 设定字符间距,取值为 NSNumber 对象,正值间距加宽,负值间距变窄
  • NSStrikethroughStyleAttributeName 设置删除线,取值为 NSNumber 对象
  • NSStrikethroughColorAttributeName 设置删除线颜色,取值为 UIColor 对象,默认值为黑色
  • NSUnderlineStyleAttributeName 设置下划线,取值为 NSNumber 对象,枚举常量 NSUnderlineStyle中的值,与删除线类似NSUnderlineColorAttributeName 设置下划线颜色,取值为 UIColor 对象,默认值为黑色
  • NSStrokeWidthAttributeName 设置笔画宽度,取值为 NSNumber 对象,负值填充效果,正值中空效果
  • NSStrokeColorAttributeName 填充部分颜色,不是字体颜色,取值为 UIColor 对象NSShadowAttributeName 设置阴影属性,取值为 NSShadow 对象
  • NSTextEffectAttributeName 设置文本特殊效果,取值为 NSString 对象,目前只有图版印刷效果可用:
  • NSBaselineOffsetAttributeName 设置基线偏移值,取值为 NSNumber ,正值上偏,负值下偏
  • NSObliquenessAttributeName 设置字形倾斜度,取值为 NSNumber ,正值右倾,负值左倾
  • NSExpansionAttributeName 设置文本横向拉伸属性,取值为 NSNumber ,正值横向拉伸文本,负值横向压缩文本
  • NSWritingDirectionAttributeName 设置文字书写方向,从左向右书写或者从右向左书写
  • NSVerticalGlyphFormAttributeName 设置文字排版方向,取值为 NSNumber 对象,0 表示横排文本,1 表示竖排文本
  • NSLinkAttributeName 设置链接属性,点击后调用浏览器打开指定URL地址
  • NSAttachmentAttributeName 设置文本附件,取值为NSTextAttachment对象,常用于文字图片混排
  • NSParagraphStyleAttributeName 设置文本段落排版格式,取值为 NSParagraphStyle 对象

//下标累加

|

这样我们就给前面的文字设置了两种不同的属性。

1.创建一个项目,创建一个类名为MyLabel,继承UILabel.

CTFrameDraw(ctFrame, contextRef);

CoreText是一个进阶的比较底层的布局文本和处理字体的技术,CoreText API在OS X v10.5 和 iOS3.2时引入,在OS X 和iOS 环境下均可以使用。

//获取点击位置所处的字符位置,就是相当于点击了第几个字符

|

let mutableAttrStr = NSMutableAttributedString(string: str)mutableAttrStr.addAttributes(dic, range: NSMakeRangemutableAttrStr.addAttributes([NSFontAttributeName:UIFont.systemFontOfSize,NSUnderlineStyleAttributeName: 1 ], range: NSMakeRangelabel.attributedText = mutableAttrStr

//弹框

<pre style="margin: 0px; tab-size: 4; white-space: pre-wrap;">NSArray * arrLines = (NSArray *)CTFrameGetLines(frame);//根据frame获取需要绘制的线的数组
NSInteger count = [arrLines count];//获取线的数量
CGPoint points[count];//建立起点的数组(cgpoint类型为结构体,故用C语言的数组)
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), points);//获取起点</pre>

不是特别复杂的需求一般情况下UILabel、UITextView都可以搞定,他们是Apple帮我们封装好的显示文本的控件,但是像复杂的图文,链接识别并替换成 "点击链接" , @someone 绑定 ,电话识别,文字大小不一 ,当然这些可以使用UIWebView,但是CoreText 技术相对于 UIWebView,有着更少的内存占用,以及可以在后台渲染的优点,非常适合用于内容的排版工作。CoreText 提供了非常高的灵活性,但是操作起来的比较复杂,学技术不就应该找最难的攻克吗?

//设置context的ctm,用于适应core text的坐标体系

必威 17

如果只能给所有的文字设置一样的属性,那这个属性字也太没劲了。我们可以给一段文字设置不同的属性

3.1 在MyLabel.m中包含  #import

origin表示的是原点 基线表示的是过原点的x轴,ascent表示的是CTRun顶线距离基线的距离,descent表示的是底线距离底线的距离

TAG标签:
版权声明:本文由必威发布于必威-编程,转载请注明出处:链接识别并替换成,必威2.文字的样式包括很多