在 Swift3 中使用带有 CGPattern 的回调时遇到问题
Trouble using callbacks with CGPattern in Swift3
我正在尝试使用 Swift 中的 CGPattern
创建彩色图案。 Apple 在 Quartz 2D Programming Guide in their section on Painting Colored Patterns 中提供了一个很好的 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
参数的正确值 CGPatternCallbacks
并且我需要将 self
作为 info
参数传递给 CGPattern
初始值设定项。
完成此操作的正确语法是什么?
让我们先看看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
}
使用彩色图案(相对于模板、非彩色图案)时,必须先设置填充颜色 space,然后再设置填充图案。
您也可以使用图案描边路径。只需使用 setStrokeColorSpace
和 setStrokePattern
.
如您所说in your answer,CGPatternDrawPatternCallback
定义为:
typealias CGPatternDrawPatternCallback =
@convention(c) (UnsafeMutableRawPointer?, CGContext) -> Void
@convention(c)
属性(只出现在生成的 header 中)意味着使用的函数值必须与 C 兼容,因此不能捕获任何上下文(因为 C 函数值只不过是指向函数的原始指针,并且不存储额外的上下文 object).
所以如果你想在函数中有可用的上下文,你需要将你自己的UnsafeMutableRawPointer?
传递给CGPattern
's initialiser的info:
参数。这将在调用时作为给定绘图模式函数的第一个参数传递。
为了给这个参数传递self
,可以使用Unmanaged
。这允许您在引用和不透明指针之间进行转换,并且与 unsafeBitCast
不同,还允许您在这样做时控制引用的内存管理。
鉴于我们无法保证 createPattern()
的调用者会保留 self
,我们 不能 将其传递给 info:
参数而不需要我们自己保留。如果它在没有保留的情况下被传递(例如使用 unsafeBitCast
),然后在绘制模式之前被释放 - 当您尝试在绘图回调。
与Unmanaged
:
您可以使用 passRetained(_:).toOpaque()
将引用作为 +1 保留不透明指针传递
您可以使用 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
heap-allocated 框中,然后再传递给 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)
}
}
这现在也解决了任何保留周期问题。
我正在尝试使用 Swift 中的 CGPattern
创建彩色图案。 Apple 在 Quartz 2D Programming Guide in their section on Painting Colored Patterns 中提供了一个很好的 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
参数的正确值 CGPatternCallbacks
并且我需要将 self
作为 info
参数传递给 CGPattern
初始值设定项。
完成此操作的正确语法是什么?
让我们先看看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
}
使用彩色图案(相对于模板、非彩色图案)时,必须先设置填充颜色 space,然后再设置填充图案。
您也可以使用图案描边路径。只需使用 setStrokeColorSpace
和 setStrokePattern
.
如您所说in your answer,CGPatternDrawPatternCallback
定义为:
typealias CGPatternDrawPatternCallback =
@convention(c) (UnsafeMutableRawPointer?, CGContext) -> Void
@convention(c)
属性(只出现在生成的 header 中)意味着使用的函数值必须与 C 兼容,因此不能捕获任何上下文(因为 C 函数值只不过是指向函数的原始指针,并且不存储额外的上下文 object).
所以如果你想在函数中有可用的上下文,你需要将你自己的UnsafeMutableRawPointer?
传递给CGPattern
's initialiser的info:
参数。这将在调用时作为给定绘图模式函数的第一个参数传递。
为了给这个参数传递self
,可以使用Unmanaged
。这允许您在引用和不透明指针之间进行转换,并且与 unsafeBitCast
不同,还允许您在这样做时控制引用的内存管理。
鉴于我们无法保证 createPattern()
的调用者会保留 self
,我们 不能 将其传递给 info:
参数而不需要我们自己保留。如果它在没有保留的情况下被传递(例如使用 unsafeBitCast
),然后在绘制模式之前被释放 - 当您尝试在绘图回调。
与Unmanaged
:
您可以使用
passRetained(_:).toOpaque()
将引用作为 +1 保留不透明指针传递
您可以使用
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
heap-allocated 框中,然后再传递给 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)
}
}
这现在也解决了任何保留周期问题。