简介:

Core Text主要用来对文本进行排版布局和字体处理,与其他UI组件相比,由于它直接与Quartz交互,因此排版效率高,渲染速度快。

下图是Core Text的架构图:

富文本实现:(GitHub传送门

接下来我们通过一个例子来看看如何实现富文本展示。我们按职责将功能拆分成几个类来完成,

1.一个显示的类,CTDisplayView,仅负责显示。

import UIKit

class CTDisplayView: UIView {

    /*
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
    }
    */
    var data:CoreTextData?
    
    override func draw(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        
        //翻转坐标系
        context?.textMatrix = CGAffineTransform.identity
        context?.translateBy(x: 0, y: self.bounds.size.height)
        context?.scaleBy(x: 1, y: -1)
        
        if data != nil {
            CTFrameDraw((self.data?.ctFrame)!, context!)
        }
    }

}

 

2.一个配置类,CTFrameParserConfig,负责配置一些默认的参数。

import UIKit

class CTFrameParserConfig: NSObject {
    var width:CGFloat = 200.0
    var fontName: NSString = "PingFangSC-Regular"
    var fontSize: CGFloat = 17.0
    var lineSpace: CGFloat = 0.0
    var textColor = ColorRGBA(r: 0, g: 0, b: 0, a: 1)
}

 

3.一个排版类,CTFrameParser,负责实现内容的排版。

class CTFrameParser: NSObject {
    
    // MARK: ------处理模板文件并返回数据源------
    class func parserTemplateFlie(_ path: NSString, config: CTFrameParserConfig) -> CoreTextData {
        let content = self.loadTemplateFile(path, config: config)
        return self.parserAttributedContent(content, config: config)
    }
    
    // MARK: ------加载模板文件------
    class func loadTemplateFile(_ path: NSString, config: CTFrameParserConfig) -> NSAttributedString {
        let data = NSData(contentsOfFile: path as String)
        let result = NSMutableAttributedString()
        if (data != nil) {
            let array:NSArray = try! JSONSerialization.jsonObject(with: data! as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSArray
            if array.isKind(of: NSArray.self) {
                for dic in array {
                    if let dict = dic as? NSDictionary {
                        let type = dict["type"]
                        if ((type as! NSString).isEqual(to:"txt")) {
                            let attributedStr = self.parserAttributedContentConvert(dict, config: config)
                            result.append(attributedStr)
                        }
                    }
                }
            }
        }
        return result
    }
    
    // MARK: ------设置文字模板描述------
    class func parserAttributedContentConvert(_ dict: NSDictionary, config: CTFrameParserConfig) -> NSAttributedString {
        let attributes:NSMutableDictionary = self.attributes(config) as! NSMutableDictionary
        
        //colorConvert
        let colorStr = (dict["color"]) as? String
        if (colorStr != nil && colorStr != "default") {
            let hexValue = Int(strtoul(colorStr, nil, 16))
            let color = ColorHEX(hexValue: hexValue)
            attributes.setObject(color, forKey: kCTForegroundColorAttributeName as! NSCopying)
        }
        
        //fontConvert
        var fontName = dict["fontName"] as? String
        var fontSize = dict["fontSize"] as? CGFloat
        if (fontName == nil || fontName == "default") {
           fontName = config.fontName as String
        }
        if (fontSize == nil || fontSize! <= 0) {
           fontSize = config.fontSize
        }
        let font = CTFontCreateWithName(fontName as CFString?, fontSize!, nil)
        attributes.setObject(font, forKey: kCTFontAttributeName as! NSCopying)
    
        let content = dict["content"] as! NSString
        
        return NSAttributedString(string: content as String, attributes: attributes.copy() as? [String : Any])
    }

    // MARK: ------设置初始化描述------
    class func attributes(_ config: CTFrameParserConfig) -> NSDictionary {
        //设置字体
        let fontName = config.fontName
        let fontSize = config.fontSize
        let fontRef = CTFontCreateWithName(fontName as CFString?, fontSize, nil)
        
        //设置行间距
        var lineSpace = config.lineSpace
        let settings: [CTParagraphStyleSetting] = [CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.lineSpacingAdjustment, valueSize: MemoryLayout<CGFloat>.size, value: &lineSpace), CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.maximumLineSpacing, valueSize: MemoryLayout<CGFloat>.size, value: &lineSpace), CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.minimumLineSpacing, valueSize: MemoryLayout<CGFloat>.size, value: &lineSpace)]
        let theParagaraphRef = CTParagraphStyleCreate(settings, 3)
        
        //设置字体颜色
        let textColor = config.textColor
        
        let dict = NSMutableDictionary()
        dict.setObject(fontRef, forKey: kCTFontAttributeName as! NSCopying)
        dict.setObject(theParagaraphRef, forKey: kCTParagraphStyleAttributeName as! NSCopying)
        dict.setObject(textColor.cgColor, forKey: kCTForegroundColorAttributeName as! NSCopying)
        
        return dict
    }
    
    // MARK: ------获取数据源------
    class func parserAttributedContent(_ content: NSAttributedString, config: CTFrameParserConfig) -> CoreTextData {
        let frameSetter = CTFramesetterCreateWithAttributedString(content)
        let restrictSize = CGSize(width: config.width, height: CGFloat(MAXFLOAT))
        let coretextSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, 0), nil, restrictSize, nil)
        let textHeight = coretextSize.height
        
        let frame = self.createFrame(frameSetter, config: config, height: textHeight)
        
        let data = CoreTextData(ctFrame: frame, height: textHeight)
        
        return data
    }
    
    // MARK: ------创建frame------
    class func createFrame(_ frameSetter: CTFramesetter, config: CTFrameParserConfig, height: CGFloat) -> CTFrame {
        let path = CGMutablePath()
        path.addRect(CGRect(x: 0, y: 0, width: config.width, height: height))
        
        let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
        
        return frame
    }

}

 

4.一个数据源,CoreTextData,负责提供显示类的所需数据。

class CoreTextData: NSObject {
    var ctFrame: CTFrame?
    var height:CGFloat = 0
    
    init(ctFrame: CTFrame, height: CGFloat) {
        self.ctFrame = ctFrame
        self.height = height
    }
}

 

此外,我们需要通过一个模板文件来自定义需要展示的文本的信息,参考如下:

 [
  {
  "type": "txt",
  "content": "This is a ",
  "fontName": "default",
  "fontSize": 0,
  "color": "default"
  },
  {
  "type": "txt",
  "content": "simple attributeString ",
  "fontName": "Snell Roundhand Bold",
  "fontSize": 26,
  "color": "0xe7292e"
  },
  {
  "type": "txt",
  "content": "demo, ",
  "fontName": "PingFangSC-Light",
  "fontSize": 18,
  "color": "0xf9f9f9"
  },
  {
  "type": "txt",
  "content": "you can use it ",
  "fontName": "PingFangSC-Bold",
  "fontSize": 13,
  "color": "0x85d64d"
  },
  {
  "type": "txt",
  "content": "by adding a json template.",
  "fontName": "Papyrus",
  "fontSize": 20,
  "color": "0x333333"
  }
  ]

然后我们去控制器里面,把displayView添加进来,完成必要的配置,

override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        let ctView = CTDisplayView()
        view.addSubview(ctView)
        
        ctView.backgroundColor = UIColor.gray
        ctView.setX(view.x())
        ctView.setY(view.y()+64)
        ctView.setWidth(width: view.width())
        
        let config:CTFrameParserConfig = CTFrameParserConfig()
        config.width = ctView.width()
        config.lineSpace = 10
        
        let path = Bundle.main.path(forResource: "template", ofType: "json")
        let data = CTFrameParser.parserTemplateFlie(path! as NSString , config: config)
        ctView.data = data
        ctView.setHeight(height: data.height)
        
    }

最后的实现效果如下:

 

内容来源于网络如有侵权请私信删除
你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!