Swift--CoreText

作者: DevKyle | 来源:发表于2015-11-01 11:32 被阅读1724次

    AttribuedString

    AttribuedString对象包含很多的属性,每一个属性都有其对应的字符区域,使用NSRange来进行描述的。

    1. NSDictionary来存放属性值
    2. range内的字符指定属性
    3. 对于控件上字符都是通过NSTextStorage实例来存储
    4. isEqual比较每个字符以及他们的属性来判等
    5. CFAttributedStringRef无缝桥接

    CoreText基础

    CoreText一开始便是定位于桌面的排版系统,使用了传统的原点在左下角的坐标系,所以它在绘制文本的时候都是参照左下角的原点进行绘制的。 但iOS的UIView的drawRect方法的context被做了次flip,如果你啥也不做处理,直接在这个context上进行CoreText绘制,你会发现文字是镜像且上下颠倒。*** Core Text本身并不支持图片绘制,图片的绘制你还得通过Core Graphics来进行***。CoreText可以通过CTRun的设置为你的图片在文本绘制的过程中留出适当的空间。这个设置就使用到CTRunDelegate了,CTRunDelegate作为CTRun相关属性或操作扩展的一个入口,使得我们可以对CTRun做一些自定义的行为。为图片留位置的方法就是加入一个空白的CTRun,自定义其ascent,descent,width等参数,使得绘制文本的时候留下空白位置给相应的图片。然后图片在相应的空白位置上使用Core Graphics接口进行绘制。
    使用CTRunDelegateCreate可以创建一个CTRunDelegate,它接收两个参数,一个是callbacks结构体,一个是所有callback调用的时候需要传入的对象。 callbacks的结构体为CTRunDelegateCallbacks,主要是包含一些回调函数,比如有返回当前run的ascent,descent,width这些值的回调函数,至于函数中如何鉴别当前是哪个run,可以在CTRunDelegateCreate的第二个参数来达到目的,因为CTRunDelegateCreate的第二个参数会作为每一个回调调用时的入参。

    CoreText对象模型

    如上图所述,其中Framesetter对应的类型是CTFramesetter,通过CFAttributedString进行初始化,它作为CTFrame对象的生产工厂,负责根据path生产对应的CTFrame。
    CTFrame是可以通过CTFrameDraw函数直接绘制到context上的,当然你可以在绘制之前,操作CTFrame中的CTLine,进行一些参数的微调。
    CTLine 可以看做Core Text绘制中的一行的对象 通过它可以获得当前行的line ascent,line descent ,line leading,还可以获得Line下的所有Glyph Runs。
    CTRun 或者叫做 Glyph Run,是一组共享相同attributes(属性)的字形的集合体
    CTFrame是指整个该UIView子控件的绘制区域,CTLine则是指每一行,CTRun则是每一段具有一样属性的字符串。比如某段字体大小、颜色都一致的字符串为一个CTRun,CTRun不可以跨行,不管属性一致或不一致。通常的结构是每一个CTFrame有多个CTLine,每一个CTLine有多个CTRun。

    CoreText实例

    1. 通过UIGraphisGetCurrentContext()获取当前的环境
    2. (可选)通过CGContextSetRGBFillColor,CGContextFillRect等方法在context上填充图形
    3. 通过CGContextConcatCTM(_:_:)来转换坐标轴。转换矩阵为(1,0;0,-1;0,self.bounds.height)
    4. 设置需要添加到context上NSMutableAttributedString属性
    5. 创建绘制区域CGPathCreateMutable(),CGPathAdd...
    6. 根据AttributedString生成CTFramesetterRef
    7. 进行绘制CTFrameDraw(_:_:)//文字的绘制,CTFrameDraw会因为行高不一致导致排版不美观

    图片的插入

    1-4同上

    1. 为图片设置CTRunDelegate,delegate决定留给图片的空间大小
    2. 设置CTRun代理CTRunDelegateCreate,并添加到一个NSMutableAttributedString实例中去。
    3. 将上述字符添加到文字中去使用NSMutableAttributedStringinsertAttributedString方法
    4. 根据AttributedString生成CTFramesetterRef,并生成相应的frame用于计算
    5. 这一步开始用来绘制图片,首先绘制图片以外的部分使用CTFrameDraw()
    6. 处理绘制图片的逻辑,从frame中获取lines ,CTFrameGetLines
    7. CTFrameGetLineOrigins把每一个CTLine的origin坐标写到数组里。//origin坐标是一个字字形的坐标,具体参考文献1
      1. 遍历CTRun找出图片所在的CTRun并进行绘制,包括获取每行的初始坐标
    8. 将当前的CTRun放入字典中来判断当前CTRun是否包含图片
    9. 如果有,那么就确定其所在位置
    10. 通过CGContextDrawImage(_:_:_:)画出图片

    Swift代码

    绘制的代码放在drawRect中去
    override func drawRect(rect: CGRect) {     
        // 步骤1:得到当前用于绘制画布的上下文,用于后续将内容绘制在画布上
        let  context  = UIGraphicsGetCurrentContext()
        // 步骤2:翻转当前的坐标系
        let transform = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty:self.bounds.height)
        CGContextConcatCTM(context, transform)
        //步骤3:创建需要绘制的文字及属性
        let str = NSMutableAttributedString(string: "在足球界,年龄是个十分奇怪的东西,你要么太年轻,要么太老;要么经验不足,要么老得什么都做不了;要么乃义务,要么就是应该做地更好。不管是成名太早,还是原地踏步太久,都会给一位球运动员造成很坏的影响。年轻人总是会受到最多的抨击,或者被球迷、主教练或者媒体榨干所有价值。最近的贝拉希诺就是一个很好的例子。这位前锋在转会窗期间犯下了一些低级的错误,每个人都对这位现年22岁的前锋有点看法,导致他从顶梁柱变成了普通的一根桩子")
        //以下为可选的部分
        //设置文字颜色
        str.addAttribute(kCTForegroundColorAttributeName as String, value:UIColor.redColor() , range: NSMakeRange(0,1))
        // 设置文字包括字体大小和字体
        let fontRef = CTFontCreateWithName("ArialMT", 20, nil)
        str.addAttribute(kCTFontAttributeName as String, value: fontRef, range:NSMakeRange(0, 1))
        
        //设置行间距
        var  lineSpacing:CGFloat = 5;
        let settings = [CTParagraphStyleSetting(spec: .LineSpacingAdjustment, valueSize: sizeof(CGFloat), value:&lineSpacing),CTParagraphStyleSetting(spec: .MaximumLineSpacing, valueSize: sizeof(CGFloat), value: &lineSpacing),CTParagraphStyleSetting(spec: .MinimumLineSpacing, valueSize: sizeof(CGFloat), value: &lineSpacing)]
        
        let theParagraphRef = CTParagraphStyleCreate(settings, 3)
        
        str.addAttribute(kCTParagraphStyleAttributeName as String, value: theParagraphRef, range: NSMakeRange(0,str.length))
        
        
        //步骤4:创建绘制区域 ,就是在self.bounds中以何种方式展示,
        let path = CGPathCreateMutable()
        //不同的绘制方法
      //        CGPathAddRect(path, nil, self.bounds)
          CGPathAddEllipseInRect(path, nil, self.bounds);
    

    以下是图片的操作,还是放在drawRect方法下。

        //(可选)步骤7:绘制图片,
        //7.1为图片设置CTRunDelegate,delegate决定留给图片的空间大小
        var imageName = "read"
        var  imageCallback =  CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (refCon) -> Void in
            NSLog("RunDelegate dealloc")
            }, getAscent: { ( refCon) -> CGFloat in
                let imageName = "read"
                refCon.initialize()
                let image = UIImage(named: imageName)
                return image!.size.height
            }, getDescent: { (refCon) -> CGFloat in
                return 0
            }) { (refCon) -> CGFloat in
                
                let imageName = String("read")
                let image = UIImage(named: imageName)
                return image!.size.width
        }
        
        
        // 7.2设置CTRun的代理
        let runDelegate = CTRunDelegateCreate(&imageCallback,&imageName)
        let imageAttributedString = NSMutableAttributedString(string: " ");//空格用于给图片留位置
        imageAttributedString.addAttribute(kCTRunDelegateAttributeName as String, value: runDelegate!, range: NSMakeRange(0, 1))
        imageAttributedString.addAttribute("imageName", value: imageName, range: NSMakeRange(0, 1))//添加属性,在CTRun中可以识别出这个字符是图片
        str.insertAttributedString(imageAttributedString, atIndex: 2)
        
        //步骤5:根据AttributedString生成CTFramesetterRef
        let frameSetter = CTFramesetterCreateWithAttributedString(str)
        let ctFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0,str.length), path, nil)
        
        //步骤6:绘制除图片以外的部分
        CTFrameDraw(ctFrame, context!)
        //7.3处理绘制图片逻辑
        let lines = CTFrameGetLines(ctFrame) as NSArray //存取frame中的ctlines
        
        let nsLinesArray: NSArray = CTFrameGetLines(ctFrame) // Use NSArray to bridge to Array
        let ctLinesArray = nsLinesArray as Array
        var originsArray = [CGPoint](count:ctLinesArray.count, repeatedValue: CGPointZero)
        let range: CFRange = CFRangeMake(0, 0)
        CTFrameGetLineOrigins(ctFrame, range,&originsArray)
        
        //7.2把ctFrame里每一行的初始坐标写到数组里
        //        CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), &origunsArray);
        
        // 7.3遍历CTRun找出图片所在的CTRun并进行绘制,每一行可能有多个
        for i in 0..<lines.count{
            //遍历每一行CTLine
            let line = lines[i]
            var lineAscent = CGFloat()
            var lineDescent = CGFloat()
            var lineLeading = CGFloat()
            CTLineGetTypographicBounds(line as! CTLineRef, &lineAscent, &lineDescent, &lineLeading)
            
    
            let runs = CTLineGetGlyphRuns(line as! CTLine) as NSArray
            for j in 0..<runs.count{
                // 遍历每一个CTRun
                var  runAscent = CGFloat()
                var  runDescent = CGFloat()
                let  lineOrigin = originsArray[i]// 获取该行的初始坐标
                
                let run = runs[j] // 获取当前的CTRun
                let attributes = CTRunGetAttributes(run as! CTRun) as NSDictionary
                let width =  CGFloat( CTRunGetTypographicBounds(run as! CTRun, CFRangeMake(0,0), &runAscent, &runDescent, nil))
                //                runRect.size.width = CGFloat( CTRunGetTypographicBounds(run as! CTRun, CFRangeMake(0,0), &runAscent, &runDescent, nil))
                // 这一段可参考Nimbus的NIAttributedLabel
                let  runRect = CGRectMake(lineOrigin.x + CTLineGetOffsetForStringIndex(line as! CTLine, CTRunGetStringRange(run as! CTRun).location, nil), lineOrigin.y - runDescent, width, runAscent + runDescent)
                let  imageNames = attributes.objectForKey("imageName")
                if imageNames is NSString {
                    let image = UIImage(named: imageName as String)
                    let  imageDrawRect = CGRectMake(runRect.origin.x, lineOrigin.y, (image?.size.width)!, (image?.size.height)!)
                    print(imageDrawRect)
                    
                CGContextDrawImage(context, imageDrawRect, image!.CGImage)
                    
                }
    
            }
            
        }
        
        
    }

    相关文章

      网友评论

        本文标题:Swift--CoreText

        本文链接:https://www.haomeiwen.com/subject/qwfqhttx.html