我的单选按钮在操场上看起来不错,但是当我把它添加到我的项目中时,看起来很奇怪.
以下是截图:
左=在操场上.
右=在我的项目.
正如你可以看到的,在右边(在我的项目中),蓝点看起来很可怕,它对于白色的圆圈来说并不光滑,同样的东西(这在黑暗的背景下不太明显).
在我的项目中,我的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在这篇文章的另一个答案中说过.由于在不同的坐标系中渲染的事实,阴影是相反的.看到他的答案来解决这个问题.