swift - framework - objective c documentation
Problemas al utilizar devoluciones de llamada con CGPattern en Swift3 (2)
Estoy intentando crear un patrón coloreado usando CGPattern
en Swift. Apple proporciona un buen ejemplo de Objective-C en la Guía de programación 2D de Quartz en su sección sobre Pintar colores . Pero obtener toda esa sintaxis convertida de Objective-C no es sencillo. Además, me gustaría utilizar el parámetro de info
en la devolución de llamada del dibujo y no hay ningún ejemplo de hacerlo.
Aquí está mi primer intento:
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
}
}
Claramente esto necesita un valor apropiado para el parámetro drawPattern
para CGPatternCallbacks
y necesito pasar self
como el parámetro info
al inicializador CGPattern
.
¿Cuál es la sintaxis adecuada para completar esto?
Comencemos mirando CGPatternDrawPatternCallback
. Se define como:
typealias CGPatternDrawPatternCallback = (UnsafeMutableRawPointer?, CGContext) -> Void
Entonces, es un cierre que toma dos parámetros: la info
y el contexto del dibujo.
Con esa información puede crear CGPatternCallback
siguiente manera:
var callbacks = CGPatternCallbacks(version: 0, drawPattern: { (info, ctx) in
// Drawing code here
}, releaseInfo: { (info) in {
// Cleanup code here
})
Pero hay algo importante que notar aquí. El cuerpo de estos cierres no puede capturar nada fuera del bloque. Si intenta hacerlo, obtendrá el siguiente error:
El punto de función de CA no se puede formar a partir de un cierre que capture el contexto
Y esta es la razón por la que el parámetro de info
debe ser utilizado. Puede pasar self
u otro objeto como parámetro de info
al crear el patrón y usarlo dentro de la devolución de llamada del dibujo. Pero esa no es una tarea simple porque simplemente no puede pasar self
como el parámetro de info
. Necesita convertirlo en el UnsafeMutableRawPointer
requerido y luego convertirlo desde el puntero dentro de la devolución de llamada del dibujo.
Aquí está el código completo con toda esa configuración:
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
}
}
Y para hacer uso de CGPattern
, puedes hacer algo como esto:
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
}
Al utilizar un patrón de color (frente a una galería de símbolos, un patrón sin color), debe establecer el espacio de color de relleno antes de establecer el patrón de relleno.
También puede usar un patrón para trazar una ruta. Simplemente use setStrokeColorSpace
y setStrokePattern
.
Como dices en tu respuesta , CGPatternDrawPatternCallback
se define como:
typealias CGPatternDrawPatternCallback =
@convention(c) (UnsafeMutableRawPointer?, CGContext) -> Void
El @convention(c)
(que solo aparece en el encabezado generado) significa que el valor de función utilizado debe ser compatible con C y, por lo tanto, no puede capturar ningún contexto (porque los valores de la función C no son más que punteros crudos a función, y no almacena un objeto de contexto adicional).
Entonces, si quiere tener contexto disponible en la función, ¿necesita pasar su propio UnsafeMutableRawPointer?
al parámetro info:
del CGPattern
de CGPattern
. Esto se pasará como el primer parámetro de la función del patrón de dibujo dado al ser llamado.
Para pasar self
a este parámetro, puede usar No Unmanaged
. Esto le permite convertir entre referencias y punteros opacos y, a diferencia de unsafeBitCast
, también le permite controlar la administración de memoria de la referencia al hacerlo.
Dado que no tenemos ninguna garantía de que la persona que llama de createPattern()
se mantendrá retenida, no podemos pasarlo al parámetro info:
sin retenerlo nosotros mismos. Si se pasó sin retener (por ejemplo, con unsafeBitCast
), y luego se desasoció antes de dibujar el patrón, obtendría un comportamiento indefinido al intentar usar un puntero colgante en la devolución de llamada del dibujo.
Con no Unmanaged
:
Puede pasar una referencia como un puntero opaco retenido +1 con
passRetained(_:).toOpaque()
Puede recuperar una referencia de este puntero con
fromOpaque(_:).takeUnretainedValue()
(y la instancia permanecerá retenida)A continuación, puede consumir el +1 retener con
fromOpaque(_:).release()
.CGPattern
hacer esto cuando seCGPattern
elCGPattern
.
Por ejemplo:
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)
}
}
Alternativamente, una mejor solución si quieres una semántica de valores para SomeShape
, podrías convertirla en una struct
. Luego, al crear el patrón, puede simplemente envolverlo en un cuadro asignado al montón de Context
antes de pasarlo al parámetro 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)
}
}
Esto ahora también se ocupa de cualquier problema de retención de ciclo.