如何复用macOS GraphicsContext绘图代码适配UIKit上下文?
坐标系适配问题:Cocoa绘图对象在UIKit中的正确显示
问题背景
Cocoa与UIKit的坐标系差异,会在UIGraphicsContext中绘制字符串时引发问题。我已编写适用于macOS的代码,可在CGContext上绘制图形基元、富文本(AttributedText)以及单个CGGlyph。为此定义了DrawingContent协议,仅要求实现draw(inContext: CGContext)方法,用容器存储遵循该协议的对象;自定义NSView持有该容器,其draw方法会遍历容器元素,调用它们的draw方法并传入当前CGContext。
在macOS未翻转坐标系时,所有元素显示正常;但直接在UIView中调用未修改的代码,图形会倒置,文本方向却未跟随翻转。尝试通过上下文变换翻转整个画布:
currentContext.translateBy(x: 0, y: rect.height) currentContext.scaleBy(x: 1, y: -1)
结果画布按预期翻转,但AttributedString显示倒置,相同字体的单个CGGlyph却显示正常。
已尝试的解决方案
- 给上下文的
textMatrix额外应用CGAffineTransform:导致原本正常的CGGlyph倒置且位置错误,AttributedText无变化。 - 绘制字符串时临时翻转上下文:
但容器无法获取视图高度,硬编码高度后文本显示正常但位置错误(两次变换相互抵消)。context.saveGState() context.scaleBy(x: 1, y: -1) context.translateBy(x: 0, y: -bounds.height) // 绘制字符串 context.restoreGState() - 在上述方案基础上手动调整文本y坐标:文本位置接近正确但存在偏差(悬于假想基线而非基线之上),且需要给绘图方法额外传递视图高度、添加iOS编译条件,违背了容器代码框架无关的初衷。
可选处理方向
- 重写全部代码适配iOS翻转坐标系,同时在macOS自定义
NSView中设置.isFlipped = true:工作量大,且仍无法解决AttributedString倒置问题。 - 寻找能统一翻转画布所有元素的方案(优先选择)。
核心问题
如何将为笛卡尔坐标系(如Cocoa/Quartz 2D)设计的绘图对象,绘制到采用翻转坐标系(如UIKit)的CGContext中,使所有类型的对象(图形基元、Glyph、字符串)都显示在正确位置且方向正确?
示例代码(macOS下模拟iOS翻转坐标系场景)
import AppKit public protocol DrawingContent { func draw(inContext context: CGContext) } public struct TestPath: DrawingContent { public func draw(inContext context: CGContext) { context.beginPath() context.setLineWidth(CGFloat(2)) context.move(to: CGPoint(x: 200, y: 30)) context.addLine(to: CGPoint(x: 250, y: 30)) context.addLine(to: CGPoint(x: 250, y: 60)) context.addLine(to: CGPoint(x: 230, y: 60)) context.strokePath() } } public struct TestText: DrawingContent { public func draw(inContext context: CGContext) { let font = CTFontCreateWithName("Helvetica" as CFString, 24, nil) var attributes: [NSAttributedString.Key: AnyObject] = [ NSAttributedString.Key.font : font, NSAttributedString.Key.foregroundColor : NSColor.black ] let attributedText = NSAttributedString(string: "Testtext", attributes: attributes) let descender = CTFontGetDescent(font) let textOrigin = CGPoint(x: 100, y: (30 - descender)) attributedText.draw(at: textOrigin) } } public struct TestGlyph: DrawingContent { public func draw(inContext context: CGContext) { let font = CTFontCreateWithName("Helvetica" as CFString, 24, nil) var position = CGPoint(x: 30, y: 30) let glyph = CGGlyph(36) // 大写字母A CTFontDrawGlyphs(font, &glyph, &position, 1, context) } } public class FlippedTestView: NSView { var drawingContent: [DrawingContent] override public var isFlipped: Bool { return true } override public init(frame: CGRect) { self.drawingContent = [TestGlyph(), TestText(), TestPath()] super.init(frame: frame) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override open func draw(_ rect: CGRect) { let currentContext = NSGraphicsContext.current!.cgContext currentContext.translateBy(x: 0, y: rect.height) currentContext.scaleBy(x: 1, y: -1) for element in drawingContent { element.draw(inContext: currentContext) } } } @main class AppDelegate: NSObject, NSApplicationDelegate { @IBOutlet var window: NSWindow! func applicationDidFinishLaunching(_ aNotification: Notification) { let testView = FlippedTestView(frame: CGRect(x: 0, y: 0, width: 300, height: 100)) self.window.contentView = testView self.window.setFrame(CGRect(x: 200, y: 200, width: 300, height: 100), display: true) } func applicationWillTerminate(_ aNotification: Notification) {} func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { return true } }
内容的提问来源于stack exchange,提问作者MassMover
相关产品推荐
相关产品推荐

