2017-05-27 38 views
3

我試圖在Swift中使用CGPattern創建一個彩色模式。 Apple在其Painting Colored Patterns的部分中的Quartz 2D Programming Guide中提供了一個很好的Objective-C示例。但是從Objective-C轉換所有的語法並不是直截了當的。另外我想在圖紙回調中使用info參數,並且沒有這樣做的例子。在Swift3中使用CGPattern回調時遇到麻煩

這是我第一次嘗試:

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參數CGPatternCallbacks一個合適的值,我需要通過selfinfo參數爲CGPattern初始化。

完成此操作的正確語法是什麼?

回答

3

如你說in your answerCGPatternDrawPatternCallback定義爲:

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

@convention(c)屬性(其僅出現在生成的頭會顯示)意味着使用的函數值必須是兼容C,因此無法捕獲任何上下文(因爲C函數值只不過是指向函數的原始指針,並且不存儲額外的上下文對象)。

因此,如果要在功能中使用上下文,則需要將自己的UnsafeMutableRawPointer?傳遞給參數CGPattern's initialiserinfo:。這將作爲被調用的給定繪圖模式函數的第一個參數傳遞。

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

既然我們都難保的createPattern()主叫方將繼續保留self,我們不能只是將其傳遞到info:參數不保留它自己。如果通過時沒有保留(例如使用unsafeBitCast),然後在繪製模式之前解除分配 - 嘗試在繪圖回調中使用懸掛指針時,您會得到未定義的行爲

隨着Unmanaged

  • 您可以通過參考作爲保留+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。然後創建模式時,你可以把它包裝起來的Context堆分配箱其傳遞到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) 
    } 
} 

現在,這也需要的任何保留週期的擔憂護理。

+0

感謝您的信息。這是我在Swift中處理「指針」的第一個。在搜索時我遇到了'unsafeBitCast'的使用。它適用於我的情況,但很高興知道有更好的方法。在我真正的代碼中,我實際上是將'CALayer'的實例作爲'info'參數傳遞。這是'createPattern'方法內創建的局部變量。它在我的代碼中使用'unsafeBitCast'工作得很好。包裝「CALayer」類型的局部變量的更安全的方法是什麼? – rmaddy

+0

@rmaddy我假設你將'CALayer'實例局部變量添加到圖層層次結構中,然後保留它(或其他一些東西保留它)?這是我能想到爲什麼它會使用'unsafeBitCast'的唯一方法。除非存在保留週期問題(即'CALayer'實例保留對'CGPattern'的引用),否則我仍然建議在將'CALayer'實例保留爲'Unmanaged'(與第一個示例中的完全相同)之前,將它傳遞給'info:'參數 - 只是防止它在繪製模式之前被釋放。 – Hamish

+0

如果存在保留週期問題,您可以隨時創建一個'Weak'類包裝器,該包裝器對給定圖層持有一個弱引用,然後傳遞保留的包裝器。然而,當然,如果給定的'CALayer'總是給定'CGPattern'的唯一擁有者,那麼你不應該做任何額外的保留(有效地使它成爲「無主」)。 – Hamish

3

我們先看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 
}) 

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

A C function point cannot be formed from a closure that captures context

這就是爲什麼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即可。

+0

maddy,你有沒有想法,爲什麼蘋果聲明CGPatternDrawPatternCallback第一個參數是指針的可變版本? – user3441734

+0

@ user3441734在Objective-C中'info'是一個普通的'void *'。我真的沒有以前的經驗,在Swift中不安全[Mutable] [Raw]指針,所以我不能肯定地說。但我的猜測是,通過將其設置爲'UnsafeMutableRawPointer'而不是'UnsafeRawPointer',它可以讓您更多地使用'info'參數以及如何使用它。如果還沒有,我建議在這個主題上發佈一個單獨的問題。 – rmaddy

+0

值得注意的是,這裏有兩個未定義行爲的主要機會。首先,如果在繪製模式之前釋放了'self',則在嘗試在繪圖回調中使用懸掛指針時會出現未定義的行爲。其次(更重要的是,因爲你有控制權),如果你將'nil'傳遞給'info:'參數,當你嘗試使用'nil'作爲非可選參考時,你會得到未定義的行爲。使用'Unmanaged',如[在我的答案](https://stackoverflow.com/a/44215903/2976878)中所示,在參考和不透明指針之間進行轉換可以防止這兩種情況。 – Hamish