Core Text 实践 +:自动换行,与字级别的控制

本文详细介绍了如何使用Core Text在iOS中实现自动换行和字级别的精细控制。通过三个示例,包括自动换行、字级别的控制A和控制B,展示了如何处理模型数据、构建映射并进行绘制,从而达到预期效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文通过 3 个例子,继续讲述 Core Text 实践上的思路

前文已经实现了控制每个字的位置,

截屏2021-03-23 下午4.28.06.png

Core Text 实践:自定义每个字的位置

一个格子,一个字

一个字,一个富文本,一个 CTLine,

先画背景图,再画文字,即可

例子一,在前文的基础上,实现自动换行

截屏2021-03-23 下午4.24.20.png

将原本的两行 “椿\n赤い椿”,

转换为一行 “椿 赤い椿”,

剩下的,就容易了

把每个字,转化为格子,

遇到空格,就跳过

模型处理

模型长这样,

struct Coupling: Decodable {
    var string: String
    let type: Int
}

数据是这样一个 list, [Coupling]

现在要做合并,

能够放在一行,就放在一行,

不能够放在一行,就换行

数据样式: 标题 , 很多格子,标题,其他(略)

        // 这个是 result
        var info = [Coupling]()
        // 放置当前的文本
        var current: Coupling?
        for jujube in list{
            switch jujube.type{
            case 4:
                // 格子
                // 拿到当前的格子,看情况
                if var c = current{
                    // 拼接当前和下一个,看看效果
                    let reduceTmp = c.string + " " + jujube.string
                    // 超过一行
                    if reduceTmp.fourW >= TextContentConst.widthInUse{
                        // 就添加当前
                        info.append(c)
                        // 更新当前文本
                        current = jujube
                    }
                    else{
                        // 不超过一行,就使用添加过的
                        c.string = reduceTmp
                        current = c
                    }
                }
                else{
                    // 进入一组格子时,取第一个
                    current = jujube
                }
            default:
                // 标题
                if let c = current{
                    info.append(c)
                    current = nil
                }
                info.append(jujube)
            }
        }
        // 补余
        // 最后的几个,可能不超过一行
        if let c = current{
            info.append(c)
        }
        renderGrid = info

这样,文本就转化好了

绘制部分
func drawGrips(m info: TxtRenderInfo, lnH lnHeight: CGFloat, index i: Int, dB startIdx: Int, lineOrigin lnOrigin: CGPoint, context ctx: CGContext, lnAscent lineAscent: CGFloat) -> CGFloat{
        // 拿到当前这一句
        let content = info.strs[i - startIdx]
        let glyphCount = content.count
        var frameImg = TextContentConst.fBgTypoImg
        let lnOffsset = (TextContentConst.padding - lnHeight) * 0.5
        var lineOrigin = lnOrigin
        lineOrigin.y -= lnOffsset
        var textP = lineOrigin
        // 处理每一个字
        for idx in 0..<glyphCount{
              // 一个字,一个 CTLine
              let pieX = String(content[idx])
              let ln = CTLineCreateWithAttributedString(pieX.word)
              let lnSize = ln.lnSize
              let typeOriginX = TextContentConst.padding * CGFloat(idx + 1)
              // 文本的原点 x 值 = 图片原点 x 值 + 偏移
              textP.x = typeOriginX + (TextContentConst.padding - lnSize.width) * 0.5
              ctx.textPosition = textP
              // 背景图的 frame
              frameImg.origin.x = typeOriginX
              frameImg.origin.y = lineOrigin.y + lineAscent - TextContentConst.fBgTypoImg.size.height + TextContentConst.offsetP.y
              if pieX != " "{
                 // 不是空格
                 // 绘制背景图
                 bgGrip?.draw(in: frameImg)
                 // 绘制文本
                 CTLineDraw(ln, ctx)
              }
        }
        return lnOffsset
    }
github repo

例子 2, 字级别的控制 A

每一行,都有合适的间距

截屏2021-03-23 下午5.27.14.png

从整个的富文本,到 CTFrame,

从 CTFrame,拆出 CTLine,

一部分 CTLine,拆出 CTRun ( 就是左边格子,右边一行文本 )

只能拿到行号

得到的数据如下: 效果比较丰富

拼音距离格子,8 pt

格子 + 文本

一行文本,都是格子

上面的规则,应用到那些 CTLine,

需要建立一个映射 map,

把 CTLine 的行号,对应的格式记录下来

        // 拼音距离格子,8 pt
        var pronounceY = [Int]()
        // 格子 + 文本, 带拼音
        var pairs = [Int]()
        // 格子 + 文本, 不带拼音
        var wArr = [Int]()
        // 一整行格子列表的,内容
        var strs = [String]()
        let cnt = list.count
        // 对于每一段内容
        var i = 0
        // 对于每一个行
        var index = 0
        // 一整行格子列表的,初始行号
        var startIdx: Int? = nil
        while i < cnt {
            let ri = list[i]
            switch ri.type {
            case 1:
                // 格子 + 文本, 带拼音
                // 占据两行
                index += 2
                pronounceY.append(index)
            case 3:
                // 格子 + 文本, 不带拼音
                index += 1
                wArr.append(index)
            case 4:
                // 一整行格子列表
                index += 1
                if startIdx == nil{
                    startIdx = index
                }
                pairs.append(index)
                strs.append(ri.string)
            default:
                // 拼音部分
                index += 1
            }
            i += 1
        }

行号 index, 先 + 1 / + 2, 再添加

因为前面还有一个 title, title 和 body 是分开的,

绘制部分
                ctx.textPosition = lineOrigin
                if info.contains(pair: i){
                    // 绘制格子 + 文本, 带拼音, 
                    // 还有,绘制格子 + 文本, 不带拼音
                    drawPairs(context: ctx, ln: line, startPoint: lineOrigin, ascent: lineAscent)
                }
                else if info.phraseY.contains(i), let startIdx = info.startIdx{
                    // 绘制一整行格子列表
                    let lnHeight = lineAscent + lineDescent + lineLeading
                    lastY -= drawGrips(m: info, lnH: lnHeight, index: i, dB: startIdx, lineOrigin: lineOrigin, context: ctx, lnAscent: lineAscent)
                }
                else{
                    // 绘制拼音
                    CTLineDraw(line, ctx)
                }
github repo

例子 3, 字级别的控制 B

每一个字符,都添加边框

截屏2021-03-24 上午11.03.05.png

分为两类

  • 一行字符,都添加边框

都给画了,每一个字符的边框, shallOmit = true

从每一个 CTLine, 获取其所有的 CTRun,

拿到 CTRun 的所有的字形 glyph,

字形 glyph 的边框位置,是这一行的起始位置 lineOrigin + 这个字在这一行中的位置 point + 这个字本身的位置 box.origin

     func drawBorders(context ctx: CGContext, ln line: CTLine, lnOrigin lineOrigin: CGPoint, omit shallOmit: Bool = true){
        ctx.setStrokeColor(UIColor.green.withAlphaComponent(0.6).cgColor)
        var notOmit = shallOmit
        // line 中每个 CTRun
        for run in line.glyphRuns{
            if notOmit{
                let font = run.font
                let glyphPositions = run.glyphPositions
                let glyphs = run.glyphs
                let glyphsBoundingRects = font.boundingRects(of: glyphs)
                
                // CTRun 中每个 glyph
                for k in 0..<glyphPositions.count {
                    let point = glyphPositions[k]
                    var box = glyphsBoundingRects[k]
                    box.origin = box.origin + point + lineOrigin
                    ctx.stroke(box)
                            
                }// for k
            }
            notOmit = true
        }//for run
    }
  • 格子 + 一行字

右边的一行字,都添加边框

shallOmit = false, 忽略掉第一个字符的边框

github repo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值