macos - कस्टमव्यू मेरी परियोजना में अजीब लग रहा है, लेकिन खेल के मैदान में अच्छा है




swift cocoa (2)

इसलिए मैंने एक सुंदर रेडियो बटन NSButton लिए एक कस्टम NSButton बनाया है, लेकिन मुझे एक बहुत ही अजीब बग का सामना करना पड़ रहा है।

मेरा रेडियो बटन खेल के मैदान में अच्छा दिखता है, लेकिन जब मैं इसे अपनी प्रोजेक्ट में जोड़ता हूं, यह अजीब लगता है।

यहां स्क्रीनशॉट दिए गए हैं:

खेल के मैदान में बाएं =
सही = मेरी परियोजना में

जैसा कि आप देख सकते हैं, दाईं ओर (मेरे प्रोजेक्ट में), नीला डॉट भयानक दिखता है , यह श्वेत सर्कल के लिए एक ही चीज़ नहीं है (यह अंधेरे पृष्ठभूमि के साथ कम दिखाई देता है)।

मेरी परियोजना में, मेरे CALayer पर NSShadow भी फ़्लिप किया गया है, भले ही मेरे मुख्य (_ कंटेनर लेयर) CALayer पर geometryFlipped फ़्लिप की गई संपत्ति सही पर सेट की गई true -> फिक्स्ड : बैनिंग्स का जवाब देखें।

import AppKit

extension NSColor {
    static func colorWithDecimal(deviceRed deviceRed: Int, deviceGreen: Int, deviceBlue: Int, alpha: Float) -> NSColor {
        return NSColor(
            deviceRed: CGFloat(Double(deviceRed)/255.0),
            green: CGFloat(Double(deviceGreen)/255.0),
            blue: CGFloat(Double(deviceBlue)/255.0),
            alpha: CGFloat(alpha)
        )
    }
}

extension NSBezierPath {

    var CGPath: CGPathRef {
        return self.toCGPath()
    }

    /// Transforms the NSBezierPath into a CGPathRef
    ///
    /// :returns: The transformed NSBezierPath
    private func toCGPath() -> CGPathRef {

        // Create path
        let path = CGPathCreateMutable()
        var points = UnsafeMutablePointer<NSPoint>.alloc(3)
        let numElements = self.elementCount

        if numElements > 0 {

            var didClosePath = true

            for index in 0..<numElements {

                let pathType = self.elementAtIndex(index, associatedPoints: points)

                switch pathType {

                case .MoveToBezierPathElement:
                    CGPathMoveToPoint(path, nil, points[0].x, points[0].y)
                case .LineToBezierPathElement:
                    CGPathAddLineToPoint(path, nil, points[0].x, points[0].y)
                    didClosePath = false
                case .CurveToBezierPathElement:
                    CGPathAddCurveToPoint(path, nil, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y)
                    didClosePath = false
                case .ClosePathBezierPathElement:
                    CGPathCloseSubpath(path)
                    didClosePath = true
                }
            }

            if !didClosePath { CGPathCloseSubpath(path) }
        }

        points.dealloc(3)
        return path
    }
}

class RadioButton: NSButton {

    private var containerLayer: CALayer!
    private var backgroundLayer: CALayer!
    private var dotLayer: CALayer!
    private var hoverLayer: CALayer!

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        self.setupLayers(radioButtonFrame: CGRectZero)
    }

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        let radioButtonFrame = CGRect(
            x: 0,
            y: 0,
            width: frameRect.height,
            height: frameRect.height
        )
        self.setupLayers(radioButtonFrame: radioButtonFrame)
    }

    override func drawRect(dirtyRect: NSRect) {
    }

    private func setupLayers(radioButtonFrame radioButtonFrame: CGRect) {
        //// Enable view layer
        self.wantsLayer = true

        self.setupBackgroundLayer(radioButtonFrame)
        self.setupDotLayer(radioButtonFrame)
        self.setupHoverLayer(radioButtonFrame)
        self.setupContainerLayer(radioButtonFrame)
    }

    private func setupContainerLayer(frame: CGRect) {

        self.containerLayer = CALayer()
        self.containerLayer.frame = frame
        self.containerLayer.geometryFlipped = true

        //// Mask
        let mask = CAShapeLayer()
        mask.path = NSBezierPath(ovalInRect: frame).CGPath
        mask.fillColor = NSColor.blackColor().CGColor
        self.containerLayer.mask = mask

        self.containerLayer.addSublayer(self.backgroundLayer)
        self.containerLayer.addSublayer(self.dotLayer)
        self.containerLayer.addSublayer(self.hoverLayer)

        self.layer!.addSublayer(self.containerLayer)
    }

    private func setupBackgroundLayer(frame: CGRect) {

        self.backgroundLayer = CALayer()
        self.backgroundLayer.frame = frame
        self.backgroundLayer.backgroundColor = NSColor.whiteColor().CGColor
    }

    private func setupDotLayer(frame: CGRect) {

        let dotFrame = frame.rectByInsetting(dx: 6, dy: 6)
        let maskFrame = CGRect(origin: CGPointZero, size: dotFrame.size)

        self.dotLayer = CALayer()
        self.dotLayer.frame = dotFrame
        self.dotLayer.shadowColor = NSColor.colorWithDecimal(deviceRed: 46, deviceGreen: 146, deviceBlue: 255, alpha: 1.0).CGColor
        self.dotLayer.shadowOffset = CGSize(width: 0, height: 2)
        self.dotLayer.shadowOpacity = 0.4
        self.dotLayer.shadowRadius = 2.0

        //// Mask
        let maskLayer = CAShapeLayer()
        maskLayer.path = NSBezierPath(ovalInRect: maskFrame).CGPath
        maskLayer.fillColor = NSColor.blackColor().CGColor

        //// Gradient
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = CGRect(origin: CGPointZero, size: dotFrame.size)
        gradientLayer.colors = [
            NSColor.colorWithDecimal(deviceRed: 29, deviceGreen: 114, deviceBlue: 253, alpha: 1.0).CGColor,
            NSColor.colorWithDecimal(deviceRed: 59, deviceGreen: 154, deviceBlue: 255, alpha: 1.0).CGColor
        ]
        gradientLayer.mask = maskLayer

        //// Inner Stroke
        let strokeLayer = CAShapeLayer()
        strokeLayer.path = NSBezierPath(ovalInRect: maskFrame.rectByInsetting(dx: 0.5, dy: 0.5)).CGPath
        strokeLayer.fillColor = NSColor.clearColor().CGColor
        strokeLayer.strokeColor = NSColor.blackColor().colorWithAlphaComponent(0.12).CGColor
        strokeLayer.lineWidth = 1.0

        self.dotLayer.addSublayer(gradientLayer)
        self.dotLayer.addSublayer(strokeLayer)
    }

    private func setupHoverLayer(frame: CGRect) {

        self.hoverLayer = CALayer()
        self.hoverLayer.frame = frame

        //// Inner Shadow
        let innerShadowLayer = CAShapeLayer()
        let ovalPath = NSBezierPath(ovalInRect: frame.rectByInsetting(dx: -10, dy: -10))
        let cutout = NSBezierPath(ovalInRect: frame.rectByInsetting(dx: -1, dy: -1)).bezierPathByReversingPath
        ovalPath.appendBezierPath(cutout)
        innerShadowLayer.path = ovalPath.CGPath
        innerShadowLayer.shadowColor = NSColor.blackColor().CGColor
        innerShadowLayer.shadowOpacity = 0.2
        innerShadowLayer.shadowRadius = 2.0
        innerShadowLayer.shadowOffset = CGSize(width: 0, height: 2)

        self.hoverLayer.addSublayer(innerShadowLayer)

        //// Inner Stroke
        let strokeLayer = CAShapeLayer()
        strokeLayer.path = NSBezierPath(ovalInRect: frame.rectByInsetting(dx: -0.5, dy: -0.5)).CGPath
        strokeLayer.fillColor = NSColor.clearColor().CGColor
        strokeLayer.strokeColor = NSColor.blackColor().colorWithAlphaComponent(0.22).CGColor
        strokeLayer.lineWidth = 2.0

        self.hoverLayer.addSublayer(strokeLayer)
    }
}

let rbFrame = NSRect(
    x: 87,
    y: 37,
    width: 26,
    height: 26
)

let viewFrame = CGRect(
    x: 0,
    y: 0,
    width: 200,
    height: 100
)

let view = NSView(frame: viewFrame)
view.wantsLayer = true
view.layer!.backgroundColor = NSColor.colorWithDecimal(deviceRed: 40, deviceGreen: 40, deviceBlue: 40, alpha: 1.0).CGColor

let rb = RadioButton(frame: rbFrame)
view.addSubview(rb)

मैं अपनी परियोजना और खेल के मैदान दोनों पर सटीक एक ही कोड का उपयोग कर रहा हूं।
यहां एक खेल का मैदान और परियोजना शामिल है।

बस स्पष्ट होने के लिए: मैं जानना चाहता हूं कि क्यों खेल के मैदान में हलकों के चित्र सरल हैं लेकिन परियोजनाओं में नहीं। (@ बैनिंग्स का उत्तर देखें, यह उनके स्क्रीनशॉटों के साथ अधिक स्पष्ट है)


मैं इसे इस लाइन कोड की जगह द्वारा तय किया है:

self.dotLayer.shadowOffset = CGSize(width: 0, height: 2)

साथ में:

self.dotLayer.shadowOffset = CGSize(width: 0, height: -2)

और innerShadowLayer.shadowOffset = CGSize(width: 0, height: 2) को प्रतिस्थापित करते हैं:

innerShadowLayer.shadowOffset = CGSize(width: 0, height: -2)

मुझे लगता है कि आपको ऐसा ही परिणाम मिलेगा:

खेल के मैदान में बाएं =
सही = मेरी परियोजना में

ऐसा लगता है कि Playground का Playground एलएलओ समन्वय प्रणाली में बेजरी पथ दिखा रहा है, आप लिंक पर जा सकते हैं: https://forums.developer.apple.com/message/39277#39277


समय लिया लेकिन मुझे लगता है कि मैं अंत में सब कुछ सोचा था या लगभग सब कुछ।

  1. पहले कुछ विज्ञान: मंडल या आर्क्स को बेज़िएर कर्जे के माध्यम से प्रदर्शित नहीं किया जा सकता। यह बेझीर क्यूवर्स की संपत्ति है जैसा कि यहां बताया गया है: https://en.wikipedia.org/wiki/Bézier_curve

    तो जब NSBezierPath(ovalInRect:) का उपयोग करते हुए आप वास्तव में एक सर्कल का अनुमान NSBezierPath(ovalInRect:) वक्र पैदा कर रहे हैं इससे उपस्थिति में अंतर हो सकता है और आकार बदलता है। फिर भी यह हमारे मामले में कोई समस्या नहीं होनी चाहिए क्योंकि अंतर दो बेझीर घटता (खेल का मैदान में एक और वास्तविक ओएस एक्स परियोजना में) के बीच है, लेकिन फिर भी मुझे यह दिलचस्प लगता है कि आपको लगता है कि चक्र नहीं है बिल्कुल सही

  2. इस प्रश्न ( दूसरा कैसपेलेयर और UIBezierPath के साथ एक चिकनी वृत्तचित्र कैसे खींचना है) के रूप में द्वितीय के रूप में, पथ के उपयोग के आधार पर एंटीलिअसिंग आपके पथ पर लागू होने के तरीके में अंतर है। NSView's drawRect: वह जगह है जहां पथ CAShapeLayer सबसे अच्छा होगा और CAShapeLayer सबसे खराब है

    इसके अलावा मैंने पाया कि CAShapeLayer दस्तावेज़ीकरण में ऐसा एक नोट है:

    आकार रास्टराइजेशन सटीकता पर गति की ओर अग्रसर हो सकता है। उदाहरण के लिए, एकाधिक अन्तर्विभाजक पथ सेगमेंट वाले पिक्सेल सटीक परिणाम नहीं दे सकते।

    ग्लेन लो का जो प्रश्न मैंने पहले उल्लेखित किया था, उसके उत्तर में हमारे मामले में ठीक काम करने लगते हैं:

    layer.rasterizationScale = 2.0 * self.window!.screen!.backingScaleFactor;
    layer.shouldRasterize = true;

    यहां मतभेद देखें:

    एक और समाधान एक चक्र को अनुकरण करने के लिए बेझियर पथ के बजाए कोने के रेडियोज़ का लाभ उठाना है, और इस समय यह बहुत सटीक है:

  3. अंत में प्लेग्राउंड और असली ओएस एक्स प्रोजेक्ट के बीच अंतर पर मेरा धारणा है कि एप्पल ने खेल का मैदान को कॉन्फ़िगर किया है ताकि कुछ अनुकूलन बंद हो जाएं ताकि CAShapeLayer का उपयोग करके पथ को सोचा भी सोचा हो सके जो सबसे अच्छा विरोधी एलियासिंग संभव हो सके। सभी के बाद आप प्रोटोटाइप कर रहे हैं, विशेष रूप से ड्राइंग ऑपरेशन पर प्रदर्शन वास्तव में महत्वपूर्ण नहीं हैं।

    मुझे इस पर सही नहीं होना चाहिए, लेकिन मुझे लगता है कि यह आश्चर्य की बात नहीं होगी। अगर किसी के पास कोई स्रोत है तो मैं खुशी से इसे जोड़ूंगा

मुझे सबसे अच्छा समाधान के लिए यदि आपको वास्तव में सबसे अच्छा सर्कल की आवश्यकता है तो कोने रेडियस का उपयोग करना है

इसके अलावा जैसा कि @ बैनिंग्स ने इस पोस्ट के दूसरे उत्तर में कहा है। एक अलग समन्वय प्रणाली में वास्तव में खेल का मैदान प्रस्तुत करने के कारण छाया को उलट कर दिया गया है। इसे ठीक करने के लिए उसका उत्तर देखें।





swift2