简介:
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 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( 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, 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( 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) }
最后的实现效果如下: