使用CoreText绘制多段文本时,如何重置坐标与变换矩阵?
解决CoreText绘制多段文本时的坐标系重置问题
先明确几个核心事实:
CGContextSaveState和CGContextRestoreState绝对能重置坐标系,你之前没效果大概率是调用时机不对——必须在任何变换操作之前调用SaveState,一段文本绘制完成后立刻RestoreState,不能在中间穿插其他变换后才恢复。TranslateCTM确实是相对变换,每次调用都是在当前坐标系基础上叠加平移,不是直接设置绝对坐标。
正确的多段文本绘制流程
按这个步骤来,绝对能避免坐标混乱:
- 初始化CGBitmapContext后,先保存一次全局初始状态(这是所有绘制的基准)
- 绘制第一段文本:
- 用
CTFramesetterSuggestFrameSizeWithConstraints计算文本的实际 bounds - 调用
CGContextSaveState(保存当前干净的坐标系) - 用
TranslateCTM平移到第一段文本的目标位置(比如居中偏移) - 调用
CTFrameDraw绘制文本 - 立刻调用
CGContextRestoreState(回到平移前的原始坐标系)
- 用
- 绘制第二段文本:
- 重复上述步骤:计算bounds → 保存状态 → 平移到第二段的目标位置 → 绘制 → 恢复状态
- 所有文本绘制完成后,可根据需要恢复全局初始状态(可选,上下文用完释放后也会失效)
你之前尝试无效的原因
- 如果是在绘制第一段之后才调用
SaveState,那保存的已经是平移后的坐标系,恢复自然没用。 MoveToPoint是针对路径绘制的,CoreText绘制依赖的是CTFrame的坐标系变换,和路径的当前点无关,所以调用它不会改变文本绘制位置。
可复用的代码示例
// 初始化CGBitmapContext CGContextRef context = CGBitmapContextCreate(...); // 保存全局初始状态 CGContextSaveState(context); // ====== 绘制第一段文本 ====== CTFramesetterRef setter1 = CTFramesetterCreateWithAttributedString(attrStr1); // 计算文本实际占用的尺寸 CGRect bounds1 = CTFramesetterSuggestFrameSizeWithConstraints(setter1, CFRangeMake(0,0), NULL, CGSizeMake(canvasWidth, CGFLOAT_MAX), NULL); // 计算居中偏移(假设画布尺寸为canvasSize) CGFloat translateX1 = (canvasSize.width - bounds1.size.width)/2; CGFloat translateY1 = (canvasSize.height - bounds1.size.height)/2; // 临时保存当前状态,避免影响后续绘制 CGContextSaveState(context); // 平移到第一段的位置 CGContextTranslateCTM(context, translateX1, translateY1); // 创建CTFrame并绘制 CTFrameRef frame1 = CTFramesetterCreateFrame(setter1, CFRangeMake(0,0), CGPathCreateWithRect(bounds1, NULL), NULL); CTFrameDraw(frame1, context); // 恢复到平移前的坐标系 CGContextRestoreState(context); CFRelease(frame1); CFRelease(setter1); // ====== 绘制第二段文本 ====== CTFramesetterRef setter2 = CTFramesetterCreateWithAttributedString(attrStr2); CGRect bounds2 = CTFramesetterSuggestFrameSizeWithConstraints(setter2, CFRangeMake(0,0), NULL, CGSizeMake(canvasWidth, CGFLOAT_MAX), NULL); // 示例:把第二段放在第一段下方10pt处,保持水平居中 CGFloat translateX2 = (canvasSize.width - bounds2.size.width)/2; CGFloat translateY2 = translateY1 + bounds1.size.height + 10; CGContextSaveState(context); CGContextTranslateCTM(context, translateX2, translateY2); CTFrameRef frame2 = CTFramesetterCreateFrame(setter2, CFRangeMake(0,0), CGPathCreateWithRect(bounds2, NULL), NULL); CTFrameDraw(frame2, context); CGContextRestoreState(context); CFRelease(frame2); CFRelease(setter2); // 恢复全局初始状态(可选) CGContextRestoreState(context); CGContextRelease(context);
额外提醒
CoreText的坐标系是左下为原点,如果是和UIKit交互(比如把绘制结果转成UIImage),记得提前用CGContextScaleCTM(context, 1, -1)翻转Y轴,这个操作要放在全局初始状态保存之前,或者单独用Save/Restore包裹,避免影响后续文本的位置计算。
内容的提问来源于stack exchange,提问作者Brad
相关产品推荐
相关产品推荐

