Problème lors de l'utilisation de rappels avec CGPattern dans Swift3




callback core-graphics (2)

Commençons par regarder CGPatternDrawPatternCallback . Il est défini comme:

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

Donc, c'est une fermeture qui prend deux paramètres - l' info et le contexte de dessin.

Avec cette information, vous pouvez créer le CGPatternCallback comme suit:

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

Mais il y a quelque chose d'important à noter ici. Le corps de ces fermetures ne peut rien capturer à l'extérieur du bloc. Si vous tentez de le faire, vous obtiendrez l'erreur suivante:

Le point de fonction AC ne peut pas être formé à partir d'une fermeture qui capture le contexte

Et c'est pourquoi le paramètre info doit être utilisé. Vous pouvez vous passer de self ou d'un autre objet en tant que paramètre info lors de la création du motif et l'utiliser dans le rappel de dessin. Mais ce n'est pas une tâche simple parce que vous ne pouvez pas simplement passer self comme paramètre info . Vous devez le faire dans le UnsafeMutableRawPointer requis, puis le convertir à partir du pointeur à l'intérieur du rappel de dessin.

Voici le code complet avec toute cette configuration:

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

Et pour utiliser le CGPattern , vous pouvez faire quelque chose comme ceci:

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
}

Lorsque vous utilisez un motif coloré (par opposition à un gabarit, un motif non coloré), vous devez définir l'espace de couleur de remplissage avant de définir le motif de remplissage.

Vous pouvez également utiliser un motif pour tracer un chemin. Utilisez simplement setStrokeColorSpace et setStrokePattern .

J'essaie de créer un motif coloré en utilisant CGPattern dans Swift. Apple propose un bel exemple d'Objective-C dans le Guide de programmation Quartz 2D dans sa section sur la peinture de motifs colorés . Mais obtenir toute cette syntaxe convertie à partir d'Objective-C n'est pas simple. De plus, je voudrais utiliser le paramètre info dans le callback de dessin et il n'y a aucun exemple de faire cela.

Voici ma première tentative:

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

Clairement, cela nécessite une valeur correcte pour le paramètre drawPattern à CGPatternCallbacks et je dois passer self comme paramètre info à l'initialiseur CGPattern .

Quelle est la syntaxe appropriée pour compléter ceci?


Comme vous le dites dans votre réponse , CGPatternDrawPatternCallback est défini comme suit:

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

L' @convention(c) (qui n'apparaît que dans l'en-tête généré) signifie que la valeur de la fonction utilisée doit être compatible avec C, et donc ne peut capturer aucun contexte (car les valeurs de la fonction C ne sont que des pointeurs bruts). fonction, et ne stocke pas d'objet contextuel supplémentaire).

Donc, si vous voulez que le contexte soit disponible dans la fonction, vous devez passer votre propre UnsafeMutableRawPointer? à l' info: paramètre de l' CGPattern de CGPattern . Ceci sera ensuite passé comme le premier paramètre de la fonction de motif de dessin donnée lors de son appel.

Pour passer à ce paramètre, vous pouvez utiliser Unmanaged . Cela vous permet de convertir entre les références et les pointeurs opaques et, contrairement à unsafeBitCast , vous permet également de contrôler la gestion de la mémoire de la référence.

Étant donné que nous n'avons aucune garantie que l'appelant de createPattern() restera self conservé, nous ne pouvons pas simplement le transmettre au paramètre info: sans le conserver nous-mêmes. Si elle a été passée sans retenue (par exemple avec unsafeBitCast ), et a ensuite été libérée avant de dessiner le motif - vous obtiendrez un comportement indéfini lorsque vous essayez d'utiliser un pointeur dangling dans le rappel de dessin.

Avec Unmanaged :

  • Vous pouvez passer une référence en tant que pointeur opaque +1 conservé avec passRetained(_:).toOpaque()

  • Vous pouvez récupérer une référence à partir de ce pointeur avec fromOpaque(_:).takeUnretainedValue() (et l'instance restera conservée)

  • Vous pouvez ensuite consommer le +1 conserver avec fromOpaque(_:).release() . Vous aurez envie de le faire lorsque le CGPattern est libéré.

Par exemple:

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

Alternativement, une meilleure solution si vous voulez une sémantique de valeur pour SomeShape , vous pourriez en faire une struct . Ensuite, lors de la création du modèle, vous pouvez simplement l'inclure dans une zone allouée au tas de Context avant de le transmettre au paramètre 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)
    }
}

Cela prend maintenant également en charge toutes les préoccupations liées au cycle de conservation.







core-graphics