swift - स्विफ्ट 3 में CGPattern के साथ कॉलबैक का उपयोग करने में समस्या




callback core-graphics (2)

स्विफ्ट में CGPattern का उपयोग करके मैं एक रंगीन पैटर्न बनाने का प्रयास कर रहा हूं। ऐप्पल क्वार्ट्ज 2 डी प्रोग्रामिंग गाइड में चित्रकारी रंगीन पैटर्न पर अपने अनुभाग में एक अच्छा उद्देश्य-सी उदाहरण प्रदान करता है। लेकिन उद्देश्य-सी से रूपांतरित किए गए सभी वाक्यविन्यास को सीधे आगे नहीं बढ़ाया जा रहा है। इसके अलावा मैं ड्राइंग कॉलबैक में 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 को CGPattern initializer को info पैरामीटर के रूप में पास करना होगा।

यह पूरा करने के लिए सही सिंटैक्स क्या है?


चलिए CGPatternDrawPatternCallback को 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
})

लेकिन यहां नोट करने के लिए कुछ महत्वपूर्ण है। इन बंदों का शरीर ब्लॉक के बाहर कुछ भी नहीं पकड़ सकता है यदि आप ऐसा करने का प्रयास करते हैं तो आपको निम्न त्रुटि मिल जाएगी:

एसी फ़ंक्शन बिंदु को बंद होने से नहीं बनाया जा सकता है जो संदर्भ को कैप्चर करता है

और यही कारण है कि 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
}

रंगीन पैटर्न (बनाम स्टैंसिल, गैर रंग का पैटर्न) का उपयोग करते समय आपको भरण पैटर्न सेट करने से पहले भरे रंग का स्थान सेट करना होगा

आप एक पैटर्न का उपयोग स्ट्रोक को पथ के लिए भी कर सकते हैं। बस setStrokeColorSpace और setStrokePattern उपयोग करें।


जैसा कि आप अपने उत्तर में कहते हैं, CGPatternDrawPatternCallback को इस प्रकार परिभाषित किया गया है:

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

@convention(c) विशेषता (जो केवल जेनरेटेड हेडर में दिखाया गया है) का अर्थ है कि फ़ंक्शन वैल्यू का उपयोग सी के साथ संगत होना चाहिए, और इसलिए किसी भी संदर्भ को कैप्चर नहीं किया जा सकता है (क्योंकि सी फंक्शन वैल्यू किसी कच्चे पॉइंटर से कुछ और नहीं है फ़ंक्शन, और अतिरिक्त संदर्भ ऑब्जेक्ट को संग्रहीत नहीं करते हैं)।

इसलिए यदि आप फ़ंक्शन में संदर्भ उपलब्ध करना चाहते हैं, तो आपको अपने स्वयं के UnsafeMutableRawPointer? को पास करना होगा UnsafeMutableRawPointer? info: CGPattern के प्रारंभिक पैरामीटर यह तब कहा जाता है जब दिए गए ड्रा पैटर्न फ़ंक्शन के पहले पैरामीटर के रूप में पारित किया जाएगा।

इस पैरामीटर से self को पार करने के लिए, आप Unmanaged उपयोग कर सकते हैं। यह आपको संदर्भों और अपारदर्शी संकेतों के बीच और unsafeBitCast विपरीत परिवर्तित करने की अनुमति देता है, इससे आपको ऐसा करने पर संदर्भ के स्मृति प्रबंधन को नियंत्रित करने की भी सुविधा मिलती है।

यह देखते हुए कि हमारे पास कोई गारंटी नहीं है कि बनाने के कॉलर createPattern() self बनाए रखेंगे, हम इसे info: पास ही नहीं निकाल सकते हैं info: पैरामीटर खुद को बनाए रखने के बिना यदि इसे बिना unsafeBitCast साथ बनाए रखा गया था, और तब पैटर्न को चित्रित करने से पहले उसे unsafeBitCast गया था - तो ड्राइंग कॉलबैक में लटकने वाले सूचक का उपयोग करने के दौरान आपको अपरिभाषित व्यवहार प्राप्त होगा।

Unmanaged साथ:

  • आप एक संदर्भ के रूप में पास passRetained(_:).toOpaque() +1) के साथ एक +1 बनाए गए अपारदर्शी पॉइंटर के रूप में पास कर सकते हैं passRetained(_:).toOpaque()

  • आप इस संकेतक से fromOpaque(_:).takeUnretainedValue() साथ एक संदर्भ वापस प्राप्त कर सकते हैं 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 ढेर-आवंटित बॉक्स में इसे लपेट कर सकते हैं 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)
    }
}

यह अब भी किसी भी बनाए रखने की चिंताओं का ख्याल रखता है।







core-graphics