Swifty CodingiOS Swift && Objective-CiOS Developer

Swift--CoreText

2015-11-01  本文已影响1724人  DevKyle

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)
                
            }

        }
        
    }
    
    
}
上一篇下一篇

猜你喜欢

热点阅读