在Swift3中使用CGPattern的回調時遇到麻煩




callback function swift (2)

我試圖在Swift中使用CGPattern創建一個彩色的模式。 在Quartz 2D Programming Guide的“ 繪製彩色圖案”一節中,Apple提供了一個很好的Objective-C示例。 但是從Objective-C轉換所有的語法並不是直截了當的。 另外我想利用繪圖回調中的info參數,沒有這樣做的例子。

這是我第一次嘗試:

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

很顯然,這需要為drawPattern參數設置適當的值,我需要將self作為info參數傳遞給CGPattern初始化程序。

什麼是正確的語法來完成這個?


我們CGPatternDrawPatternCallback一下CGPatternDrawPatternCallback 。 它被定義為:

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

所以這是一個閉包,需要兩個參數 - info和繪圖上下文。

有了這些信息,您可以創建CGPatternCallback ,如下所示:

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

但是這裡有一些重要的事情需要注意。 這些關閉的身體無法捕捉任何東西。 如果你嘗試這樣做,你會得到以下錯誤:

AC函數點不能由捕獲上下文的閉包形成

這就是為什麼需要使用info參數。 創建模式時,您可以將self或其他對像作為info參數傳遞,並在繪圖回調中使用它們。 但這不是一個簡單的任務,因為你不能簡單地通過self作為info參數。 您需要將其設置為所需的UnsafeMutableRawPointer ,然後將其從圖形回調中的指針轉換回來。

以下是所有設置的完整代碼:

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

而要使用CGPattern ,你可以這樣做:

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
}

當使用彩色圖案(與模板,非彩色圖案相比)時,必須在設置填充圖案之前設置填充顏色空間。

您也可以使用一個模式來描述路徑。 只需使用setStrokeColorSpacesetStrokePattern


正如你在回答中所說的那樣 , CGPatternDrawPatternCallback被定義為:

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

@convention(c)屬性(只出現在生成的頭文件中)意味著所使用的函數值必須與C兼容,因此不能捕獲任何上下文(因為C函數值只不過是指向函數,並且不存儲額外的上下文對象)。

所以如果你想要在函數中有上下文,你需要傳遞你自己的UnsafeMutableRawPointer?CGPattern的初始化程序info:參數。 這將作為被調用的給定繪圖模式函數的第一個參數傳遞。

為了將self傳遞給這個參數,你可以使用Unmanaged 。 這允許您在引用和不透明指針之間進行轉換,並且與unsafeBitCast不同,還可以在執行此操作時控制引用的內存管理。

鑑於我們不能保證createPattern()的調用者將保持self保留,我們不能僅僅將它傳遞給info:參數而不保留它自己。 如果它沒有被保留unsafeBitCast (例如使用unsafeBitCast ),然後在繪製模式之前就被釋放了 - 當您在繪圖回調中嘗試使用懸掛指針時,您會得到未定義的行為

Unmanaged

  • 你可以通過passRetained(_:).toOpaque()傳遞一個引用作為+1保留的不透明指針passRetained(_:).toOpaque()

  • 你可以從fromOpaque(_:).takeUnretainedValue() (這個實例將保持保留狀態)

  • 然後,您可以使用fromOpaque(_:).release()消費+1保留。 當CGPattern被釋放時,你會想要這樣做。

例如:

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

或者,如果你想要SomeShape值語義,你可以把它變成一個struct 。 然後,在創建模式時,可以在將其傳遞給info:參數之前,將其包裝在Context堆分配框中:

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

這現在也考慮到任何保留週期問題。





core-graphics