如何围绕指定点绘制旋转后的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
相关产品推荐
相关产品推荐

