CustomView在我的项目中看起来很奇怪,但在操场上很好

Vin*_*oil 15 macos cocoa nsview swift swift2

所以我创建了一个NSButton有一个漂亮的单选按钮的自定义,但我遇到了一个非常奇怪的错误.

我的单选按钮在操场上看起来不错,但是当我将它添加到我的项目时,它看起来很奇怪.

这是截图:

单选按钮 单选按钮x400

=在操场上.
=在我的项目中.

正如你所看到的,在右边(在我的项目中),蓝点看起来很可怕,它不是光滑的,对于白色圆圈来说是相同的(在深色背景下它不太明显).

在我的项目中,即使我的main(_containerLayer_)上的属性设置为NSShadow,我CALayer的也会被翻转.geometryFlippedCALayertrue- > 固定:见@Bannings的回答.

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)
Run Code Online (Sandbox Code Playgroud)

我在我的项目和操场上使用完全相同的代码.
这是一个包含游乐场和项目的拉链.

需要明确的是:我想知道为什么圆形图画在操场上是平滑的而不是在项目中.(参见@Bannings的答案,截图更明显)

Fan*_*ude 16

花了很多时间,但我想我终于想出了一切或几乎所有的东西.

  1. 首先是一些科学:圆形或弧形不能通过贝塞尔曲线表示.这是Bézier曲线的属性,如下所述:https://en.wikipedia.org/wiki/Bézier_curve

    因此,在使用时,NSBezierPath(ovalInRect:)您实际上会生成近似圆的Bézier曲线.这可能导致外观和形状呈现方式的差异.在我们的情况下,这应该不是问题,因为两个Bézier曲线(Playground中的曲线和实际OS X项目中的曲线)之间存在差异,但我仍然觉得有趣的是,如果您认为圆圈不是足够完美.

  2. 其次如本问题所述(如何使用CAShapeLayer和UIBezierPath绘制平滑圆),根据路径的使用位置,抗锯齿将应用于路径的方式有所不同.NSView's drawRect:是所在的路径抗锯齿将是这个地方最好,并CAShapeLayer作为最糟糕的.

    另外我发现CAShapeLayer文档有一条说明:

    形状光栅化可能有利于速度而不是准确性.例如,具有多个相交路径段的像素可能无法给出精确结果.

    Glen Low对我之前提到的问题的回答似乎在我们的案例中运作良好:

    layer.rasterizationScale = 2.0 * self.window!.screen!.backingScaleFactor;
    layer.shouldRasterize = true;
    
    Run Code Online (Sandbox Code Playgroud)

    看到这里的差异:

    简单的Bézier路径

    Bézier路径与前面提到的修复

    另一个解决方案是利用角半径而不是Bézier路径来模拟圆,这次它非常准确:

    使用半径

  3. 最后,我对Playground和真实OS X项目之间差异的假设是Apple配置了Playground以便关闭一些优化,因此即使是使用绘制的路径也CAShapeLayer可以获得最佳的抗锯齿效果.毕竟你是原型制作,表演并不是特别重要,特别是在绘画操作上.

    我不确定是对的,但我认为这并不奇怪.如果有人有任何来源我很乐意添加它.

对我来说,如果你真的需要最好的圆圈,最好的解决方案是使用角半径.

另外正如@Bannings在这篇文章的另一个答案中所述.由于操场在不同的坐标系中渲染,因此阴影被反转.看到他解决这个问题的答案.

  • 谢谢您的回答.非常完整,我很感激.我现在使用角半径方法,它工作得很好. (2认同)

Ban*_*ngs 9

我只是通过替换此行代码修复它:

self.dotLayer.shadowOffset = CGSize(width: 0, height: 2)
Run Code Online (Sandbox Code Playgroud)

有:

self.dotLayer.shadowOffset = CGSize(width: 0, height: -2)
Run Code Online (Sandbox Code Playgroud)

并替换innerShadowLayer.shadowOffset = CGSize(width: 0, height: 2)为:

innerShadowLayer.shadowOffset = CGSize(width: 0, height: -2)
Run Code Online (Sandbox Code Playgroud)

我想你会得到同样的结果:

在此输入图像描述 在此输入图像描述

=在操场上.
=在我的项目中.

它似乎Playground显示了LLO坐标系中的bezier路径,您可以访问链接:https: //forums.developer.apple.com/message/39277#39277