swift - 在 Swift3 中使用带有 CGPattern 的回调时遇到问题

标签 swift callback core-graphics

我正在尝试使用 CGPattern 创建彩色图案在 swift 。 Apple 在 Quartz 2D Programming Guide 中提供了一个很好的 Objective-C 示例在他们关于 Painting Colored Patterns 的部分.但是从 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 的参数初始值设定项。

完成此操作的正确语法是什么?

最佳答案

如你所说in your answer , CGPatternDrawPatternCallback 定义为:

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

@convention(c)属性(它只出现在生成的头文件中)意味着使用的函数值必须与 C 兼容,因此不能捕获任何上下文(因为 C 函数值只不过是指向函数的原始指针,并且不存储一个额外的上下文对象)。

因此,如果您想在函数中使用上下文,您需要将自己的UnsafeMutableRawPointer? 传递给CGPattern's initialiserinfo: 参数。 .这将在调用时作为给定绘图模式函数的第一个参数传递。

为了将self传递给这个参数,你可以使用Unmanaged .这允许您在引用和不透明指针之间进行转换,并且与 unsafeBitCast 不同,还允许您在执行此操作时控制引用的内存管理。

鉴于我们无法保证 createPattern() 的调用者会保留 self,我们不能将它传递给info: 参数而不需要我们自己保留。如果它在没有保留的情况下传递(例如使用 unsafeBitCast),然后在绘制模式之前被释放 - 当您尝试在绘图回调。

使用非托管:

  • 您可以使用 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。然后在创建模式时,您可以在将其传递给 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)
    }
}

这现在也解决了任何保留周期问题。

关于swift - 在 Swift3 中使用带有 CGPattern 的回调时遇到问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44211720/

相关文章:

ios - 雕刻线画好后怎么让边缘变成纯色

ios - 设备方向改变时重绘 CoreGraphics 绘图

ios - UIView.animateWithDuration 或 UIScrollView.animateWithDuration - 如何在当前状态暂停

swift - 如何在 Swift 结构中定义 Firestore 引用类型

javascript - Angular2 中回调函数中作用域变量的访问值

C 回调和非 Go 线程

c++ - 如何在 C++ 中获取通过 C 回调返回的结果

ios - 访问调整大小图像的原始像素数据时出错

ios - 如何在具有多个部分的 TableView 中使用 searchBar?

ios - 来自 Parse 的标签不会更新——Swift 1.2