首页 > 解决方案 > 应该使用什么 CALayer draw(in ctx:) 坐标以正确的比例在正确的点上绘制?

问题描述

我有一个 NSView,它使用 CALayers 创建内容并希望以矢量格式生成 pdf 输出。首先这是可能的,其次苹果在他们的文档中引用的逻辑坐标系是什么,最后你应该如何让 CALayers 默认背景和边框属性来绘制?如果我使用这些属性,那么它们不会被绘制到上下文中,如果我绘制边框,那么这会复制属性设置。关于何时应该或不应该使用这些属性,或者在创建矢量输出(例如 pdf 文档)时是否应该使用 CALayers 有点令人困惑。

该视图具有以下层:

下面列出了 NSView 类和 CALayer 子类。

视图层和绘图层绘制在正确的位置,但所有绘图层子视图都绘制在错误的位置并且大小错误。

我认为这是因为绘图层应用了一个变换,而下面的绘图没有考虑到这一点。这是问题吗?我将如何将图层转换应用于 CGContext - 如果这是解决方案 - 将事情放在正确的位置?

class DrawingView: NSView {

    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
        guard let context = NSGraphicsContext.current?.cgContext else {
            return
        }
        // In theory should generate vector graphics
        self.layer?.draw(in: context)
        //Or
        // Generates bitmap image
        //self.layer?.render(in: context)
    }
}

class DrawingLayer: CALayer {

    override func draw(in ctx: CGContext) {
        drawBorder(in: ctx)
        if let subs = self.sublayers {
            for sub in subs {
                sub.draw(in: ctx)
            }
        }
    }
    func drawBorder(in ctx: CGContext){
        let rect = self.frame

        if let background = self.backgroundColor {
            ctx.setFillColor(background)
            ctx.fill(rect)
        }
        if let borders = self.borderColor {
            ctx.setStrokeColor(borders)
            ctx.setLineWidth(self.borderWidth)
            ctx.stroke(rect)
        }
    }
}

标签: vectorcalayernsview

解决方案


这是我的解决方案 - 使用不同的功能在 PDF 中绘图。我仍然不确定我是否理解为什么系统在绘制到 pdf 和屏幕时表现不一致。

编辑: - 部分问题似乎是我在 draw() 函数中使用图层框架来计算大小/位置,但是当图层被转换时,它的框架会改变大小和形状以适应原始旋转的矩形!所以提示 - 将您的原始矩形保存在其他地方,然后当您应用旋转或使用边界时,您的绘图不会突然变得奇怪!

EDIT2: - 所以 render(in:) 有效,“有点” - 一些 3D 变换被忽略,例如 CATransform3DMakeTranslation()。旋转变换似乎很幸运,所以只需确保设置正确的原点以避免需要平移变换。

在 pdf 中绘制时,CALayers 调用 NSView 的 draw(dirtyRect:) 函数 draw(in:) 函数不会自动被调用,因此您似乎必须自己在层次结构中一直调用它们,记住翻译每个子图层的坐标系 - 如果它们在其本地坐标系中绘制。并且请注意,这并不能保证 pdf 中的矢量输出 - 似乎某些内容(如文本)被渲染为位图。如果有人对事物的行为方式和原因有解释,请解释。

无论如何,我刚刚回退到使用 CALayer.render(in:) 函数,该函数导致整个层层次结构被渲染 - 并且仍然带有一些矢量和一些位图元素!

class DrawingView: NSView {

    var drawingLayer: DrawingLayer? {
        return self.layer?.sublayers?[0] as? DrawingLayer
    }

    override func draw(_ dirtyRect: NSRect) {
        //super.draw(dirtyRect)
        guard let context = NSGraphicsContext.current?.cgContext else {
            return
        }
        // In theory should generate vector graphics
        self.drawingLayer?.drawPdf(in: context)

        //Or
        // Generates bitmap image
        //self.layer?.render(in: context)
    }
}

class DrawingLayer: CALayer {

   var fillColor: CGColor?
   var lineColor: CGColor?
   var lineWidth: CGFloat = 0.5
    var scale: CGFloat = 1.0

    // No need for this since we don't need to call draw for each sublayer
    // that gets done for us!
    override func draw(in ctx: CGContext) {
        print("drawing DrawingLayer  \(name ?? "")")

//        if let subs = self.sublayers {
//            for sub in subs {
//                sub.draw(in: ctx)
//            }
//        }
    }
    func drawPdf(in ctx: CGContext) {


        ctx.saveGState()
        ctx.translateBy(x: self.frame.minX, y:  self.frame.minY)

       if let subs = self.sublayers {
           for sub in subs {

               (sub as! NormalLayer).drawPdf(in: ctx)
           }
       }

        ctx.restoreGState()
    }
}
class NormalLayer: CALayer {

    var fillColor: CGColor?
       var lineColor: CGColor?
       var lineWidth: CGFloat = 0.5
    var scale: CGFloat = 1.0

    override func draw(in ctx: CGContext) {
        drawBorder(in: ctx)
    }
    func drawBorder(in ctx: CGContext){
        let bds = self.bounds
        let rect = CGRect(x: bds.minX*scale, y: bds.minY*scale, width: bds.width*scale, height: bds.height*scale)
        print("drawing NormalLayer \(name ?? "")  \(rect)")

        if let background = self.fillColor {
            ctx.setFillColor(background)
            ctx.fill(rect)
        }
        if let borders = self.lineColor {
            ctx.setStrokeColor(borders)
            ctx.setLineWidth(self.lineWidth)
            ctx.stroke(rect)
        }
    }
    func drawPdf(in ctx: CGContext) {
        ctx.saveGState()
        ctx.translateBy(x: self.frame.minX, y:  self.frame.minY)
        drawBorder(in: ctx)
        ctx.restoreGState()
    }
}

推荐阅读