相对于UIKit,使用coretext绘制文本效率高,具有更高的自由度,可随时插入图片,增加文本点击事件等。

1.增加文本的点击事件

思路:定义UILabel子类,设置可点击的富文本range及其他属性(颜色、字体),touchBegin方法中根据点击位置判断所在行所在index,最后判断index是否在range内,若在,则响应事件。

  首先定义可点击的文本model,该model主要含有3个属性,string-用于回调显示,range-用于判断位置,attributes-用于绘制文本

class LinkAttributesModel:NSObject {
var string:String!
var range:NSRange!
var attributes:Dictionary<NSAttributedStringKey,Any>!
}

  接着在label子类实现方法setString(string:String,attributes:Dictionary<NSAttributedStringKey,Any>?,linkArr:Array<LinkAttributesModel>),第一个参数是显示string,第二个参数是string的属性,第三个参数是一个可点击文本的集合,需要手动赋值

// 声明
func setString(string:String,attributes:Dictionary<NSAttributedStringKey,Any>?,linkArr:Array<LinkAttributesModel>) {
locLinkArr = linkArr;
let attributedString = NSMutableAttributedString(string: string, attributes: attributes)
for linkMdl in linkArr {
attributedString.addAttributes(linkMdl.attributes, range: linkMdl.range)
}
// self.attributedText = attributedString
tmpAttributedString = attributedString // create frameRef
let frameSetter = CTFramesetterCreateWithAttributedString(attributedString)
let path = CGMutablePath()
path.addRect(self.bounds)
frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, string.count), path, nil) self.isUserInteractionEnabled = true
} // 调用
// 构造属性model
var mdlArr = [LinkAttributesModel]()
for i in 0...2 {
let mdl = LinkAttributesModel()
let loc = i*10
let range = NSMakeRange(loc, 5)
mdl.range = range
let startIdx = showString.index(showString.startIndex, offsetBy: range.location)
let endIdx = showString.index(startIdx, offsetBy: range.length)
mdl.string = String(showString[startIdx...endIdx])
let randomColor = UIColor(red: CGFloat(arc4random_uniform(256))/255.0, green: CGFloat(arc4random_uniform(256))/255.0, blue: CGFloat(arc4random_uniform(256))/255.0, alpha: 1)
mdl.attributes = [NSAttributedStringKey.foregroundColor:randomColor]
mdlArr.append(mdl)
} let lbl = Taplabel(frame:self.view.bounds.insetBy(dx: 0, dy: 50))
let style = NSMutableParagraphStyle()
style.lineSpacing = 10
lbl.setString(string: showString, attributes: [NSAttributedStringKey.font:UIFont.systemFont(ofSize: 20),NSAttributedStringKey.paragraphStyle:style], linkArr: mdlArr)

  最后根据属性文本创建对应的frameRef,然后遍历其CTLine数组,判断点击的位置在哪行内,再根据x获取当前行的偏移位置index,最后遍历linkAttributesModel,判断range是否包含index,如果包含,则触发回调事件

func touchEvents(pt:CGPoint) {
if (frameRef == nil) {return}
let lines:[CTLine] = CTFrameGetLines(frameRef!) as! [CTLine]
let lineCount = lines.count
if (lineCount == 0) {return}
// let flipTransform = CGAffineTransform(translationX: 0, y: self.bounds.size.height).scaledBy(x: 1, y: -1)
for i in 0..<lineCount {
let line = lines[i]//CFArrayGetValueAtIndex(lines, i) as! CTLine
var ascent:CGFloat = 0
var descent:CGFloat = 0
var leading:CGFloat = 0
let width = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, &leading))
let height = ascent + abs(descent) + leading var rect = CGRect(x: pt.x, y: pt.y, width: width, height: height)
// var rect = flippedRect.applying(flipTransform) var lineSpace:CGFloat = 0;
if let style = tmpAttributedString?.attribute(NSAttributedStringKey.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle{
lineSpace = style.lineSpacing
} rect.origin.y = (height+lineSpace)*CGFloat(i); if rect.contains(pt) {
var stringIndex = CTLineGetStringIndexForPosition(line, pt)
var offset:CGFloat = 0
CTLineGetOffsetForStringIndex(line, stringIndex, &offset)
if (offset > pt.x) {
stringIndex = stringIndex - 1
} for linkMdl in locLinkArr! {
if linkMdl.range.contains(stringIndex) {
tapBlock!(linkMdl.string)
}
}
}
}
}

  上述代码的主要核心在于

   a.算行高  CTLineGetTypographicBounds(line,&asent,&descent,&leading)

   b.算行内偏移  CTLineGetStringIndexForPosition(line,pt)  如果根据该index算出的偏移量大于pt.x,则index-1

   c.判断range包含  range.contains(index)

2.图文绘制

思路:NSAttributedString中插入占位空白符-关联图片属性。图片宽高由CTRunDelegate,图片可藏于追加属性中。ctx.draw(image,in:drawRect)时遍历CTLine,寻找含有CTRunDelegate的CTRun,如果存在,则获取图片的相应数组,构造drawRect和image绘制图片。

  追加图片属性

func appendImage(attrStr:NSAttributedString)->NSAttributedString {
var callBacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (pointer) in
}, getAscent: { (ref) -> CGFloat in
let dic = ref.assumingMemoryBound(to: NSDictionary.self)
return dic.pointee.object(forKey: "height") as! CGFloat
}, getDescent: { (ref) -> CGFloat in
return 0
}) { (ref) -> CGFloat in
let dic = ref.assumingMemoryBound(to: NSDictionary.self)
return dic.pointee.object(forKey: "width") as! CGFloat
} let imageData:Dictionary<String,Any> = ["height":100,"width":200]
let imageDataPointer = UnsafeMutablePointer<Dictionary<String,Any>>.allocate(capacity: 1)
imageDataPointer.initialize(to: imageData)
let runDelegate = CTRunDelegateCreate(&callBacks, imageDataPointer) let runAttributes:[NSAttributedStringKey:Any] = [kCTRunDelegateAttributeName as NSAttributedStringKey:runDelegate as Any]
let runAttributeStr = NSMutableAttributedString(string: " ", attributes: runAttributes)
runAttributeStr.addAttribute(NSAttributedStringKey(rawValue: "imageName"), value: "shot", range: NSMakeRange(0, 1)) let mutableAttrStr = NSMutableAttributedString(attributedString: attrStr)
let whiteSpaceStr = createAttributedString(str: "临时换行\n")
let suffixStr = createAttributedString(str: "\nhaha insert image succeed")
mutableAttrStr.append(whiteSpaceStr)
mutableAttrStr.append(runAttributeStr)
mutableAttrStr.append(suffixStr)
return mutableAttrStr
}

  绘制追加的图片属性

func drawImage(frame:CTFrame,ctx:CGContext) {
let lines = CTFrameGetLines(frame) as Array
var lineOrigins:Array<CGPoint> = Array(repeating: .zero, count: lines.count)
CTFrameGetLineOrigins(frame, CFRange(location: 0, length: 0), &lineOrigins)
for i in 0..<lines.count {
let line = lines[i] as! CTLine
let runs = CTLineGetGlyphRuns(line) as Array
print("lineCount == \(lines.count) i == \(i)\n")
for j in 0..<runs.count {
let run = runs[j] as! CTRun
let runAttribute = CTRunGetAttributes(run) as NSDictionary
// if runAttribute == nil {return}
print("runCount == \(runs.count) j == \(j) runattribute == \(runAttribute)")
let runDelegate = runAttribute.object(forKey: kCTRunDelegateAttributeName as String)
if (runDelegate == nil) {continue}
var ascent:CGFloat = 0
var descent:CGFloat = 0
let width:CGFloat = CGFloat(CTRunGetTypographicBounds(run, CFRange(location: 0, length: 0), &ascent, &descent, nil))
let xoffset = lineOrigins[i].x + CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil)
let yoffset = lineOrigins[i].y
let drawRect = CGRect(x: xoffset, y: yoffset, width: width, height: ascent+descent)
if let imgName = runAttribute.object(forKey: "imageName") as? String {
let img = UIImage(named: imgName)
ctx.draw((img?.cgImage)!, in: drawRect)
}
}
}
}

  关键代码是

  a.获取lineOrigin  CTFrameGetLineOrigins(frame, CFRange(location: 0, length: 0), &lineOrigins)

  b.获取width,height  CTRunGetTypographicBounds(run, CFRange(location: 0, length: 0), &ascent, &descent, nil)  //图片忽略leading属性

  c.获取x偏移量  lineOrigins[i].x+CTLineGetOffsetForStringIndex(line,CTRunGetStringRange(run).location,nil)

  d.获取图片  runAttributes.object(forKey:"imageName")

coretext简单使用的更多相关文章

  1. CoreText 简单 使用

    - (void)drawRect:(CGRect)rect { NSString *longText = @"CoreText"; /* ... */ NSRange rang = ...

  2. 图文混排--CoreText的简单运用

    常见的在一些微博微信中可以看见一段文字中有不同的字体,字体有不同的颜色,并且可能会有一些笑脸之类的表情,这些可以通过图文混排做到. 图文混排可以通过WebView和CoreText做到,其他还有别的方 ...

  3. 简单的Coretext 图文混排

    在很多新闻类或有文字展示的应用中现在都会出现图文混排的界面例如网易新闻等,乍一看去相似一个网页,其实这样效果并非由UIWebView 加载网页实现.现在分享一种比较简单的实现方式 iOS sdk中为我 ...

  4. 12.24笔记(关于//UIDynamic演练//多对象的附加行为//UIDynamic简单演练//UIDynamic//(CoreText框架)NSAttributedString)

          12.24笔记1.UIDynamic注意点:演示代码:上面中设置视图旋转的时候,需要注意设置M_PI_4时,视图两边保持平衡状态,达不到仿真效果.需要偏移下角度.2.吸附行为3.推动行为初 ...

  5. iOS:基于CoreText的排版引擎

    一.CoreText的简介 CoreText是用于处理文字和字体的底层技术.它直接和Core Graphics(又被称为Quartz)打交道.Quartz是一个2D图形渲染引擎,能够处理OSX和iOS ...

  6. 【iOS】使用CoreText实现图文混排

    iOS没有现成的支持图文混排的控件,而要用多个基础控件组合拼成图文混排这样复杂的排版,是件很苦逼的事情.对此的解决方案有使用CoreText进行绘制,或者使用TextKit.本文主要讲解对于CoreT ...

  7. CoreText原理及基本使用方法

    关于富文本的排版也是现在的一个技术点,以下是近日关于CoreText的学习记录以及个人理解,希望能对正在学习CoreText的朋友起到帮助. 1.框架坐标系 首先让我们先来看看CoreText坐标系和 ...

  8. iOS阅读器实践系列(一)coretext纯文本排版基础

    前言:之前做了公司阅读类的App,最近有时间来写一下阅读部分的实现过程,供梳理逻辑,计划会写一个系列希望能涉及到尽量多的方面与细节,欢迎大家交流.吐槽.拍砖,共同进步. 阅读的排版用的是coretex ...

  9. 基于CoreText的基础排版引擎

    storyboard: 新建一个CTDisplayView:UIView 代码如下: #import "CTDisplayView.h" #import "CoreTex ...

随机推荐

  1. 动态更换animator的animatorcontroller

    你可以这样 Animator animator = this.gameObject.GetComponent<Animator>(); animator.runtimeAnimatorCo ...

  2. 使用Typora编写Markdown你真的会了吗

    目录 Typora 介绍 使用 常用快捷键 概述 标题 一级标题 二级标题 方式(推荐) 一级标题 二级标题 三级标题 四级标题 五级标题 六级标题 段落 粗体斜体删除线 下划线 注释 分割线 脚注 ...

  3. Activiti中工作流的生命周期详细解析!一个BPMN流程示例带你认识项目中流程的生命周期

    BPMN 2.0介绍 业务流程模型注解(BusinessProcess Modeling Notation - BPMN)是业务流程模型的一种标准图形注解.这个标准是由对象管理组(Object Man ...

  4. Spring Boot - 自定义 Banner 图案

    我是陈皮,一个在互联网 Coding 的 ITer,微信搜索「陈皮的JavaLib」第一时间阅读最新文章,回复[资料],即可获得我精心整理的技术资料,电子书籍,一线大厂面试资料和优秀简历模板. 前言 ...

  5. 华为计算平台MDC810发布量产

    华为计算平台MDC810发布量产 塞力斯的发布会刚刚结束,会上塞力斯SF5自由远征版也确实让人眼前一亮. 全球首款4S级加速能力.1000+km续航新能源作为这款车的卖点. 续航1000+km成了最近 ...

  6. 开源软硬一体OpenCV AI Kit(OAK)

    开源软硬一体OpenCV AI Kit(OAK) OpenCV 涵盖图像处理和计算机视觉方面的很多通用算法,是非常有力的研究工具之一,且稳居开发者最喜爱的 AI 工具/框架榜首. 1.会不会被USA禁 ...

  7. 如何在CPU上优化GEMM(下)

    如何在CPU上优化GEMM(下) Array Packing 另一个重要的技巧是数组打包.这个技巧是对数组的存储维度进行重新排序,将某个维度上的连续访问模式在平滑后转换为顺序模式. 如上图所示,在阻塞 ...

  8. 如果攻击者操控了 redirect_uri,会怎样?

    读者在看这篇文章之前,请先了解 Oauth2.0 的 Authorization Code 授权流程,可以看 Authorization Code 授权原理和实现方法 在 Token Enpoint ...

  9. java后端知识点梳理——web安全

    跨域 当浏览器执行脚本时会检查是否同源,只有同源的脚本才会执行,如果不同源即为跨域. 这里的同源指访问的协议.域名.端口都相同. 同源策略是由 Netscape 提出的著名安全策略,是浏览器最核心.基 ...

  10. Task02:基础查询与排序

    2.1 SELECT语句基础 2.1.1 从表中选取数据 SELECT语句 从表中选取数据时需要使用SELECT语句,也就是只从表中选出(SELECT)必要数据的意思.通过SELECT语句查询并选取出 ...