coretext简单使用
阅读原文时间:2023年07月08日阅读:2

相对于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!
}

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

// 声明
func setString(string:String,attributes:Dictionary?,linkArr:Array) {
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 = 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")

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章