Trouble using callbacks with CGPattern in Swift3

后端 未结 2 1383
有刺的猬
有刺的猬 2020-12-06 21:24

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

2条回答
  •  谎友^
    谎友^ (楼主)
    2020-12-06 22:11

    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.

提交回复
热议问题