Problem using callbacks with CGPattern in Swift3

I am trying to create a colored drawing using CGPattern

in Swift. Apple provides a good Objective-C example in the Quartz 2D Programming Guide under Painting Color Pictures . But getting all this syntax converted from Objective-C is not straight forward. Plus I would like to use a parameter info

in the drawing callback and there is no example of this.

Here's my first try:

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
    }
}

      

Obviously, this needs a value drawPattern

for CGPatternCallbacks

, and I need to pass self

as a parameter info

to the initializer CGPattern

.

What is the correct syntax for this?

+3


source to share


2 answers


As you say in your answer , CGPatternDrawPatternCallback

is defined as:

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

      

The attribute @convention(c)

(which only appears in the generated header) means that the function value used must be C compatible, and therefore cannot capture any context (since C function values ​​are nothing more than original function pointers and do not store an additional object context).

So, if you want to have the context available in the function, you need to pass your own UnsafeMutableRawPointer?

in the info:

CGPattern

initializer
parameter . It will then be passed as the first parameter to this drawing template function after being called.

To pass this parameter self

, you can use Unmanaged

. This allows you to convert between links and opaque pointers and, unlike it unsafeBitCast

, also allows you to control the memory management of the link while doing so.

Given that we do not guarantee that the caller will createPattern()

save the self

saved one, we cannot simply pass it to a parameter info:

without saving it ourselves. If it was passed in without saving (for example with a help unsafeBitCast

) and then freed before drawing the template, you will get undefined behavior when you try to use a dangling pointer in a drawing callback.

From Unmanaged

:



  • You can pass the link as a +1 saved opaque pointer with passRetained(_:).toOpaque()

  • You can return a link from that pointer with fromOpaque(_:).takeUnretainedValue()

    (and the instance will remain saved)

  • Then you can use save +1 with fromOpaque(_:).release()

    . You will want to do this when you are free CGPattern

    .

For example:

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)
    }
}

      

Alternatively, a better solution, if you want value semantics for SomeShape

, you can do it struct

. Then when you create your template, you can simply wrap it in a Context

heap allocated field before passing it to a parameter 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)
    }
}

      

Now this also takes care of any loop preservation issues.

+3


source


Let's start by looking at it CGPatternDrawPatternCallback

. It is defined as:

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

      

Thus, it is a closure that takes two parameters - info

and a drawing context.

With this information, you can create CGPatternCallback

like this:

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

      

But there is something important here. The body of these gates cannot capture anything outside the block. If you try to do this, you will receive the following error:

Function point C cannot be formed from a closure that captures a context



And so a parameter must be used info

. You can pass self

or some other object as a parameter info

when creating the template and use it inside the drawing callback. But this is not an easy task, because you cannot simply pass self

as a parameter info

. You need to do it as required UnsafeMutableRawPointer

and then convert back from the pointer inside the drawing callback.

Here's the complete code with all this setup:

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
    }
}

      

And to use CGPattern

, you can do something like this:

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
}

      

When using a color pattern (versus stencil, non-color pattern), you must set the fill color space before setting the fill pattern.

You can also use a pattern to pat the path. Just use setStrokeColorSpace

and setStrokePattern

.

+3


source







All Articles