I\'m attempting to create a colored pattern using CGPattern
in Swift. Apple provides a nice Objective-C example in the Quartz 2D Programming Guide in their sect
Let's start by looking at CGPatternDrawPatternCallback
. It is defined as:
typealias CGPatternDrawPatternCallback = (UnsafeMutableRawPointer?, CGContext) -> Void
So it is a closure that takes two parameters - the info
and the drawing context.
With that info you can create the CGPatternCallback
as follows:
var callbacks = CGPatternCallbacks(version: 0, drawPattern: { (info, ctx) in
// Drawing code here
}, releaseInfo: { (info) in {
// Cleanup code here
})
But there is something important to note here. The body of these closures can't capture anything outside of the block. If you attempt to do so you will get the following error:
A C function point cannot be formed from a closure that captures context
And this is why the info
parameter needs to be used. You can pass self
or some other object as the info
parameter when creating the pattern and use that inside the drawing callback. But that's not a simple task because you can't simply pass self
as the info
parameter. You need to make it into the required UnsafeMutableRawPointer
and then convert it back from the pointer inside the drawing callback.
Here's the complete code with all of that 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 make use of the 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 colored pattern (versus a stencil, non-colored pattern) you must set the fill color space before setting the fill pattern.
You can also use a pattern to stroke a path. Just use setStrokeColorSpace
and setStrokePattern
.
As you say in your answer, CGPatternDrawPatternCallback
is defined as:
typealias CGPatternDrawPatternCallback =
@convention(c) (UnsafeMutableRawPointer?, CGContext) -> Void
The @convention(c) attribute (which only appears to show up in the generated header) means that the function value used must be compatible with C, and therefore cannot capture any context (because C function values are nothing more than raw pointers to a function, and don't store an additional context object).
So if you want to have context available in the function, you need to pass your own UnsafeMutableRawPointer?
to the info:
parameter of CGPattern's initialiser. This will then be passed as the first parameter of the given draw pattern function upon being called.
In order to pass self
to this parameter, you can use Unmanaged. This allows you to convert between references and opaque pointers and, unlike unsafeBitCast
, also lets you control the memory management of the reference when doing so.
Given that we have no guarantee that the caller of createPattern()
will keep self
retained, we cannot just pass it off to the info:
parameter without retaining it ourselves. If it were passed without retaining (for example with unsafeBitCast
), and was then deallocated before drawing the pattern – you would get undefined behaviour when attempting to use a dangling pointer in the drawing callback.
With Unmanaged
:
You can pass a reference as a +1 retained opaque pointer with passRetained(_:).toOpaque()
You can get back a reference from this pointer with fromOpaque(_:).takeUnretainedValue()
(and the instance will stay retained)
You can then consume the +1 retain with fromOpaque(_:).release()
. You'll want to do this when the CGPattern
is freed.
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 could make it a struct
. Then when creating the pattern, you can just wrap it up in a Context
heap-allocated box before passing it off to the info:
parameter:
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)
}
}
This now also takes care of any retain cycle concerns.