我的单选按钮在操场上看起来不错,但是当我把它添加到我的项目中时,看起来很奇怪.
以下是截图:
左=在操场上.
右=在我的项目.
正如你可以看到的,在右边(在我的项目中),蓝点看起来很可怕,它对于白色的圆圈来说并不光滑,同样的东西(这在黑暗的背景下不太明显).
在我的项目中,我的CALayer上的NSShadow也被翻转,即使我的main(_containerLayer_)CALayer上的geometryFlipped属性设置为true. – >固定:请参阅@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,points[0].y) didClosePath = false case .CurveToBezierPathElement: CGPathAddCurveToPoint(path,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,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,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)
我在我的项目和操场上都使用完全相同的代码.
Here is a zip包含游乐场和项目.
只是为了清楚:我想知道为什么圈子图画在操场上顺利,但不是在项目中. (参见@Bannings答案,他的截图更明显)
>首先一些科学:圆形或圆弧不能通过贝塞尔曲线来表示.这是Bézier曲线的属性,如下所示:https://en.wikipedia.org/wiki/Bézier_curve
所以当使用NSBezierPath(ovalInRect :)时,你实际上生成了一个近似圆的贝塞尔曲线.这可能会导致外观上的差异以及形状的变化.在我们的案例中,这不应该是一个问题,因为区别在于两个贝塞尔曲线(Playground中的一个曲线和真正的OS X项目中的曲线),但是我觉得有趣的是,如果你认为圈子不是足够完美
>如本question (How to draw a smooth circle with CAShapeLayer and UIBezierPath)中所述,根据路径的使用位置,抗锯齿方式将适用于您的路径存在差异. NSView的drawRect:作为路径抗锯齿最好的地方,CAShapeLayer是最差的.
另外我发现CAShapeLayer documentation有一个注释说:
Shape rasterization may favor speed over accuracy. For example,pixels with multiple intersecting path segments may not give exact results.
Glen Low对我之前提到的问题的回答似乎在我们的案例中工作正常:
layer.rasterizationScale = 2.0 * self.window!.screen!.backingScaleFactor; layer.shouldRasterize = true;
请看这里的差异:
另一个解决方案是利用角半径而不是贝塞尔路径来模拟一个圆圈,这次它是非常准确的:
>最后我假设游乐场和真正的OS X项目是苹果公司配置的Playground,以便一些优化被关闭,所以使用CAShapeLayer甚至想到的路径可以获得最佳的抗锯齿功能.毕竟你是原型,表演并不重要,特别是绘画操作.
我不知道这是对的,但我认为这并不奇怪.如果有人有任何来源,我乐意添加它.
对我来说,如果你真的需要最好的圈子,最好的解决方案是使用拐角半径.
还有@Bannings在这篇文章的另一个答案中说过.由于在不同的坐标系中渲染的事实,阴影是相反的.看到他的答案来解决这个问题.