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

如何复用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编译条件,违背了容器代码框架无关的初衷。

可选处理方向

  1. 重写全部代码适配iOS翻转坐标系,同时在macOS自定义NSView中设置.isFlipped = true:工作量大,且仍无法解决AttributedString倒置问题。
  2. 寻找能统一翻转画布所有元素的方案(优先选择)。

核心问题

如何将为笛卡尔坐标系(如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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 16:26:00