在Swift3中使用CGPattern回调时遇到问题

时间:2017-05-27 00:25:50

标签: swift callback core-graphics

我正在尝试使用Swift中的CGPattern创建彩色图案。 Apple在Quartz 2D Programming Guide的部分Painting Colored Patterns中提供了一个很好的Objective-C示例。但是从Objective-C转换所有语法并不是直截了当的。另外,我想在绘图回调中使用info参数,并且没有这样做的例子。

这是我的第一次尝试:

class SomeShape {
    func createPattern() -> CGPattern? {
        let bounds = CGRect(x: 0, y: 0, width: someWidth, height: someHeight)
        let matrix = CGAffineTransform.identity
        var callbacks = CGPatternCallbacks(version: 0, drawPattern: nil, releaseInfo: nil)

        let res = CGPattern(info: nil, bounds: bounds, matrix: matrix, xStep: bounds.width, yStep: bounds.height, tiling: .noDistortion, isColored: true, callbacks: &callbacks)

        return res
    }
}

显然,drawPattern参数需要CGPatternCallbacks的正确值,我需要将self作为info参数传递给CGPattern初始值设定项。

完成此操作的正确语法是什么?

2 个答案:

答案 0 :(得分:5)

正如您所说in your answerCGPatternDrawPatternCallback定义为:

typealias CGPatternDrawPatternCallback =
                               @convention(c) (UnsafeMutableRawPointer?, CGContext) -> Void

@convention(c)属性(仅显示在生成的标题中)表示所使用的函数值必须与C兼容,因此无法捕获任何上下文(因为C函数值只不过是原始的指向函数的指针,并且不存储其他上下文对象。)

因此,如果您希望在函数中提供上下文,则需要将自己的UnsafeMutableRawPointer?传递给CGPattern's initialiserinfo:参数。然后,这将在被调用时作为给定绘制模式函数的第一个参数传递。

要将self传递给此参数,您可以使用Unmanaged。这允许您在引用和不透明指针之间进行转换,与unsafeBitCast不同,它还允许您在执行此操作时控制引用的内存管理。

鉴于我们无法保证createPattern()的来电者保留self,我们无法将其传递给info:参数而不保留它自己。如果它是在没有保留的情况下传递的(例如使用unsafeBitCast),然后在绘制模式之前取消分配 - 在尝试在绘图回调中使用悬空指针时,将获得未定义的行为

使用Unmanaged

  • 您可以使用passRetained(_:).toOpaque()

  • 将引用作为+1保留的不透明指针传递
  • 您可以使用fromOpaque(_:).takeUnretainedValue()从此指针返回引用(并保留实例)

  • 然后,您可以使用fromOpaque(_:).release()消费+1保留。当CGPattern被释放时,你会想要这样做。

例如:

class SomeShape {
    // the bounds of the shape to draw
    let bounds = CGRect(x: 0, y: 0, width: 40, height: 40)

    func createPattern() -> CGPattern? {

        var callbacks = CGPatternCallbacks(version: 0, drawPattern: { info, ctx in

            // cast the opaque pointer back to a SomeShape reference.
            let shape = Unmanaged<SomeShape>.fromOpaque(info!).takeUnretainedValue()

            // The code to draw a single tile of the pattern into "ctx"...
            // (in this case, two vertical strips)
            ctx.saveGState()
            ctx.setFillColor(UIColor.red.cgColor)
            ctx.fill(CGRect(x: 0, y: 0,
                            width: shape.bounds.width / 2, height: shape.bounds.height))

            ctx.setFillColor(UIColor.blue.cgColor)
            ctx.fill(CGRect(x: 20, y: 0,
                            width: shape.bounds.width / 2, height: shape.bounds.height))
            ctx.restoreGState()

        }, releaseInfo: { info in
            // when the CGPattern is freed, release the info reference,
            // consuming the +1 retain when we originally passed it to the CGPattern.
            Unmanaged<SomeShape>.fromOpaque(info!).release()
        })

        // retain self before passing it off to the info: parameter as an opaque pointer.
        let unsafeSelf = Unmanaged.passRetained(self).toOpaque()

        return CGPattern(info: unsafeSelf, bounds: bounds, matrix: .identity,
                         xStep: bounds.width, yStep: bounds.height,
                         tiling: .noDistortion, isColored: true, callbacks: &callbacks)
    }
}

或者,如果您想要SomeShape的值语义,则可以使用struct作为更好的解决方案。然后在创建模式时,您可以将它包装在Context堆分配的框中,然后再将其传递给info:参数:

struct SomeShape {

    // the bounds of the shape to draw
    let bounds = CGRect(x: 0, y: 0, width: 40, height: 40)

    func createPattern() -> CGPattern? {

        final class Context {
            let shape: SomeShape
            init(_ shape: SomeShape) { self.shape = shape }
        }

        var callbacks = CGPatternCallbacks(version: 0, drawPattern: { info, ctx in

            // cast the opaque pointer back to a Context reference,
            // and get the wrapped shape instance.
            let shape = Unmanaged<Context>.fromOpaque(info!).takeUnretainedValue().shape

            // ...

        }, releaseInfo: { info in
            // when the CGPattern is freed, release the info reference,
            // consuming the +1 retain when we originally passed it to the CGPattern.
            Unmanaged<Context>.fromOpaque(info!).release()
        })

        // wrap self in our Context box before passing it off to the info: parameter as a
        // +1 retained opaque pointer.
        let unsafeSelf = Unmanaged.passRetained(Context(self)).toOpaque()

        return CGPattern(info: unsafeSelf, bounds: bounds, matrix: .identity,
                         xStep: bounds.width, yStep: bounds.height,
                         tiling: .noDistortion, isColored: true, callbacks: &callbacks)
    }
}

现在也可以解决任何保留周期问题。

答案 1 :(得分:3)

让我们先来看看CGPatternDrawPatternCallback。它被定义为:

typealias CGPatternDrawPatternCallback = (UnsafeMutableRawPointer?, CGContext) -> Void

因此它是一个带有两个参数的闭包 - info和绘图上下文。

使用该信息,您可以按如下方式创建CGPatternCallback

var callbacks = CGPatternCallbacks(version: 0, drawPattern: { (info, ctx) in
    // Drawing code here
}, releaseInfo: { (info) in {
    // Cleanup code here
})

但这里有一些重要的事情需要注意。这些闭合的主体无法捕获块外的任何东西。如果您尝试这样做,您将收到以下错误:

  

无法从捕获上下文的闭包中形成C函数点

这就是需要使用info参数的原因。在创建模式时,您可以将self或其他对象作为info参数传递,并在绘图回调中使用它。但这不是一项简单的任务,因为您不能简单地将self作为info参数传递。您需要将其转换为所需的UnsafeMutableRawPointer,然后将其从绘图回调中的指针转换回来。

以下是包含所有设置的完整代码:

class SomeShape {
    func createPattern() -> CGPattern? {
        let bounds = CGRect(x: 0, y: 0, width: someWidth, height: someHeight) // The size of each tile in the pattern
        let matrix = CGAffineTransform.identity // adjust as needed
        var callbacks = CGPatternCallbacks(version: 0, drawPattern: { (info, ctx) in
            let shape = unsafeBitCast(info, to: SomeShape.self)

            // The needed drawing code to draw one tile of the pattern into "ctx"
        }, releaseInfo: { (info) in 
            // Any cleanup if needed
        })

        let unsafeSelf = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)

        let res = CGPattern(info: unsafeSelf, bounds: bounds, matrix: matrix, xStep: bounds.width, yStep: bounds.height, tiling: .noDistortion, isColored: true, callbacks: &callbacks)

        return res
    }
}

要使用CGPattern,您可以执行以下操作:

func draw(_ ctx: CGContext) {
    // Any other needed setup

    let path = CGPath(....) // some path

    // Code to fill a path with the pattern
    ctx.saveGState()
    ctx.addPath(path) // The path to fill

    // Setup the pattern color space for the colored pattern
    if let cs = CGColorSpace(patternBaseSpace: nil) {
        ctx.setFillColorSpace(cs)
    }

    // Create and apply the pattern and its opacity
    if let fillPattern = someShapeInstance.createPattern() {
        var fillOpacity = CGFloat(1.0)
        ctx.setFillPattern(fillPattern, colorComponents: &strokeOpacity)
    }

    ctx.fillPath(using: theDesiredFillRule)
    ctx.restoreGState()

    // Any other drawing
}

使用彩色图案(相对于模板,非彩色图案)时,必须在设置填充图案之前设置填充颜色空间。

您还可以使用模式来描边路径。只需使用setStrokeColorSpacesetStrokePattern