CoreText 学习2

本文主要是用Swift重写了巧哥博客中的 Demo,博客的原始链接如下:

唐巧:
基于 CoreText 的排版引擎:基础
基于 CoreText 的排版引擎:进阶

1、支持图文(链接)混排的排版引擎

content.json文件修改为:

[
  {
    "type": "img",
    "width": 200,
    "height": 108,
    "name": "coretext-image-1.jpg"
  },
  {
    "color": "blue",
    "content": " 更进一步地,实际工作中,我们更希望通过一个排版文件,来设置需要排版的文字的 ",
    "size": 16,
    "type": "txt"
  },
  {
    "color": "red",
    "content": " 内容、颜色、字体 ",
    "size": 22,
    "type": "txt"
  },
  {
    "color": "black",
    "content": " 大小等信息。\n",
    "size": 16,
    "type": "txt"
  },
  {
    "type": "img",
    "width": 200,
    "height": 130,
    "name": "coretext-image-2.jpg"
  },
  {
    "color": "default",
    "content": " 我在开发猿题库应用时,自己定义了一个基于 UBB 的排版模版,但是实现该排版文件的解析器要花费大量的篇幅,考虑到这并不是本章的重点,所以我们以一个较简单的排版文件来讲解其思想。",
    "type": "txt"
  },
  {
    "color": "default",
    "content": " 这在这里尝试放一个参考链接:",
    "type": "txt"
  },
  {
    "color": "blue",
    "content": " 链接文字 ",
    "url": "http://blog.devtang.com",
    "type": "link"
  },
  {
    "color": "default",
    "content": " 大家可以尝试点击一下 ",
    "type": "txt"
  }
]

修改parseTemplateFile方法,增加一个名为imageArray的参数来保存解析的图片信息,增加一个名为linkArray的参数来保存解析的链接信息:

/// 解析模板文件
class func parseTemplateFile(path: String, config: CTFrameParserConfig) -> CoreTextData {
    var imageArray = [CoreTextImageData]()
    var linkArray  = [CoreTextLinkData]()
    
    let content = self.loadTemplateFile(path: path, config: config, imageArray: &imageArray, linkArray: &linkArray)
    
    let coreTextData = self.parse(content: content, config: config)
    
    coreTextData.imageArray = imageArray
    coreTextData.linkArray = linkArray
    
    return coreTextData
}

修改loadTemplateFile方法,增加了对于typeimglink的节点处理逻辑:

/// 加载模板文件
class func loadTemplateFile(path: String, config: CTFrameParserConfig, imageArray: inout [CoreTextImageData], linkArray: inout [CoreTextLinkData]) -> NSAttributedString {
    
    let result = NSMutableAttributedString()
    
    let url = URL(fileURLWithPath: Bundle.main.path(forResource: path, ofType: "json")!)
    if let data = try? Data(contentsOf: url) {
        
        if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: .allowFragments), let array = jsonObject as? [[String: String]] {
            for item in array {
                let type = item["type"]
                
                if type == "txt" {
                    let subStr = self.parseAttributedCotnentFromDictionary(dict: item, config: config)
                    result.append(subStr)
                }
                
                if type == "image" {
                    let imageData = CoreTextImageData()
                    imageData.name = item["name"]
                    imageData.imagePosition = CGRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0)
                    imageArray.append(imageData)
                    
                    let subStr = self.parseImageAttributedCotnentFromDictionary(dict: item, config: config)
                    result.append(subStr)
                }
                
                if type == "link" {
                    let startPosition = result.length
                    let subStr = self.parseAttributedCotnentFromDictionary(dict: item, config: config)
                    result.append(subStr)
                    
                    var linkData = CoreTextLinkData()
                    linkData.title = item["content"]
                    linkData.url   = item["url"]
                    linkData.range = NSMakeRange(startPosition, result.length - startPosition)
                    linkArray.append(linkData)
                }
            }
        }
    }
    
    return result
}

最后我们新建一个最关键的方法:parseImageAttributedCotnentFromDictionary,生成图片空白的占位符,并且设置其CTRunDelegate信息。其代码如下:

/// 从字典中解析图片富文本信息
///
/// - Parameters:
///   - dict: 文字属性字典
///   - config: 配置信息
/// - Returns: 图片富文本
class func parseImageAttributedCotnentFromDictionary(dict: [String: String], config: CTFrameParserConfig) -> NSAttributedString {
    var ascender: CGFloat = 0.0
    if let height = (dict["height"] as AnyObject).floatValue {
        ascender = CGFloat(height)
    }
    var width: CGFloat = 0.0
    if let w = (dict["width"] as AnyObject).floatValue {
        width = CGFloat(w)
    }
    let pic = PictureRunInfo(ascender: ascender, descender: 0.0, width: width)
    
    var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { refCon in
        print("RunDelegate dealloc!")
    }, getAscent: { (refCon) -> CGFloat in
        let pictureRunInfo = unsafeBitCast(refCon, to: PictureRunInfo.self)
        return pictureRunInfo.ascender
    }, getDescent: { (refCon) -> CGFloat in
        return 0
    }, getWidth: { (refCon) -> CGFloat in
        
        let pictureRunInfo = unsafeBitCast(refCon, to: PictureRunInfo.self)
        return pictureRunInfo.width
    })
    
    let selfPtr = UnsafeMutableRawPointer(Unmanaged.passRetained(pic).toOpaque())
    
    // 创建 RunDelegate, delegate决定留给图片的空间大小
    let runDelegate = CTRunDelegateCreate(&callbacks, selfPtr)
    
    let attributes : Dictionary = self.attributes(config: config)
    // 创建一个空白的占位符
    let space = NSMutableAttributedString(string: " ", attributes: attributes)
    
    CFAttributedStringSetAttribute(space, CFRangeMake(0, 1), kCTRunDelegateAttributeName, runDelegate)
    return space
}

接着我们对CoreTextData进行改造:

// 用于保存由 CTFrameParser 类生成的 CTFrame 实例以及 CTFrame 实际绘制需要的高度
class CoreTextData: NSObject {
    
    var ctFrame: CTFrame
    var height: CGFloat
    var imageArray: [CoreTextImageData] = [CoreTextImageData]() {

        willSet {
            fillImagePosition(imageArray: newValue)
        }
        
    }
    var linkArray: [CoreTextLinkData]?
    
    init(ctFrame: CTFrame, height: CGFloat) {
        self.ctFrame = ctFrame
        self.height = height
    }
     
    private func fillImagePosition(imageArray: [CoreTextImageData]) {
        if imageArray.count == 0 {
            return
        }
        
        let lines = CTFrameGetLines(ctFrame) as Array
        var originsArray = [CGPoint](repeating: CGPoint.zero, count:lines.count)
        // 把 CTFrame 里每一行的初始坐标写到数组里
        CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), &originsArray)
        
        var imgIndex : Int = 0
        var imageData: CoreTextImageData? = imageArray[0]
        
        for index in 0..

2、添加对图片的点击支持

CTDisplayView类增加:

private func setupEvents() {
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(userTapGestureDetected(recognizer:)))
    self.addGestureRecognizer(tapGestureRecognizer)
    self.isUserInteractionEnabled = true
}

func userTapGestureDetected(recognizer: UITapGestureRecognizer) {
    let point = recognizer.location(in: self)

    if let imageArray = data?.imageArray {
        for imageData in imageArray {
            // 翻转坐标系,因为 imageData 中的坐标是 CoreText 的坐标系
            let imageRect = imageData.imagePosition
            var imagePosition = imageRect.origin
            imagePosition.y = self.bounds.size.height - imageRect.origin.y - imageRect.size.height
            let rect = CGRect(x: imagePosition.x, y: imagePosition.y, width: imageRect.size.width, height: imageRect.size.height)
            if rect.contains(point) {
                print("\(imageData.name)")
                
                break
            }
        }
    }
}
CoreText 学习2_第1张图片
最终展示.png

Github地址:
https://github.com/GuiminChu/JianshuExamples/tree/master/CoreTextDemo

你可能感兴趣的:(CoreText 学习2)