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?
source to share
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 freeCGPattern
.
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.
source to share
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
.
source to share