You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何围绕指定点绘制旋转后的NSAttributedString?

解决文本与红线同角度且位于其正上方的绘制问题

我明白你的需求——要让文本和红线保持相同角度,并且精准地放在红线正上方,之前直接用context.rotate(by:)导致旋转中心不对的问题很常见,核心是旋转前要先把坐标系平移到你想要的旋转锚点,这样旋转才会围绕目标点进行。

关键思路

要实现这个效果,你需要按以下顺序操作上下文变换:

  • 先将坐标系平移到文本的绘制锚点(比如红线的起点,或者文本的中心位置)
  • 执行旋转操作(使用红线对应的弧度)
  • 再将坐标系平移回原位置的反向偏移,这样后续的绘制操作就会以锚点为中心旋转

另外,因为要让文本在红线正上方,你需要在绘制时给文本的y坐标加上一个向上的偏移量(比如文本高度的一半或者合适的间距)。

修改后的完整代码

class DrawView: UIView {
    override func draw(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()!
        
        // 绘制红线
        context.move(to: CGPoint(x: 290, y: 650))
        context.addLine(to: CGPoint(x: 530, y: 530))
        context.setStrokeColor(UIColor.red.cgColor) // 显式设置红线颜色,方便调试
        context.strokePath()
        
        // 翻转坐标系适配Core Text
        context.textMatrix = .identity
        context.translateBy(x: 0, y: bounds.size.height)
        context.scaleBy(x: 1.0, y: -1.0)
        
        // 1. 计算红线的关键参数
        let lineStart = CGPoint(x: 290, y: 650)
        let lineEnd = CGPoint(x: 530, y: 530)
        let lineLength = hypot(lineEnd.x - lineStart.x, lineEnd.y - lineStart.y) // 红线长度,和你之前的268一致
        let lineAngle = atan2(lineEnd.y - lineStart.y, lineEnd.x - lineStart.x) // 计算红线的弧度,和你的0.4636一致
        
        // 2. 设置文本属性
        let justification = NSMutableParagraphStyle()
        justification.alignment = .center
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont(name: "Arial", size: 22.0)!,
            .foregroundColor: UIColor.black,
            .paragraphStyle: justification
        ]
        let attrString = NSAttributedString(string: "12032.00", attributes: attributes)
        
        // 3. 准备文本绘制的路径和锚点
        let textHeight: CGFloat = 30 // 你之前定义的高度
        let textOffsetAboveLine: CGFloat = 5 // 文本和红线之间的间距,可调整
        // 文本的绘制起点:红线起点x,y轴向上偏移(文本高度+间距)
        let textStart = CGPoint(x: lineStart.x, y: lineStart.y - textHeight - textOffsetAboveLine)
        let textPath = CGMutablePath()
        textPath.addRect(CGRect(x: textStart.x, y: textStart.y, width: lineLength, height: textHeight))
        
        // 4. 执行上下文变换:平移到旋转锚点 → 旋转 → 平移回原位置
        context.saveGState() // 保存当前上下文状态,方便后续恢复
        // 旋转锚点设为文本区域的中心(也可以用红线起点,根据需求调整)
        let rotationAnchor = CGPoint(x: textStart.x + lineLength/2, y: textStart.y + textHeight/2)
        context.translateBy(x: rotationAnchor.x, y: rotationAnchor.y)
        context.rotate(by: lineAngle)
        context.translateBy(x: -rotationAnchor.x, y: -rotationAnchor.y)
        
        // 5. 绘制文本
        let framesetter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString)
        let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrString.length), textPath, nil)
        CTFrameDraw(frame, context)
        
        context.restoreGState() // 恢复上下文状态,不影响后续绘制
    }
}

代码解释

  • 坐标系变换:使用saveGState()和restoreGState()来隔离旋转操作,避免影响其他绘制内容。先平移到旋转锚点(文本中心),旋转后再平移回去,这样文本就会以该锚点为中心旋转。
  • 角度计算:用atan2重新计算红线角度,确保和红线的实际角度完全匹配,比硬编码0.4636更灵活。
  • 文本位置调整:通过textOffsetAboveLine控制文本和红线的间距,保证文本在红线正上方。
  • 路径范围:文本的路径宽度和红线长度一致,配合居中对齐,让文本刚好覆盖红线上方的区域。

这样修改后,文本就会和红线保持相同角度,并且精准地位于红线正上方啦!

内容的提问来源于stack exchange,提问作者LarsErikWik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:17:23