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

前端之家收集整理的这篇文章主要介绍了osx – CustomView在我的项目中看起来很奇怪,但在操场上很好前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
所以我创建了一个自定义的NSButton来有一个漂亮的单选按钮,但我遇到一个非常奇怪的bug.

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

以下是截图:


左=在操场上.
右=在我的项目.

正如你可以看到的,在右边(在我的项目中),蓝点看起来很可怕,它对于白色的圆圈来说并不光滑,同样的东西(这在黑暗的背景下不太明显).

在我的项目中,我的CALayer上的NSShadow也被翻转,即使我的main(_containerLayer_)CALayer上的geometryFlipped属性设置为true. – >固定:请参阅@Bannings答案.

  1. import AppKit
  2.  
  3. extension NSColor {
  4. static func colorWithDecimal(deviceRed deviceRed: Int,deviceGreen: Int,deviceBlue: Int,alpha: Float) -> NSColor {
  5. return NSColor(
  6. deviceRed: CGFloat(Double(deviceRed)/255.0),green: CGFloat(Double(deviceGreen)/255.0),blue: CGFloat(Double(deviceBlue)/255.0),alpha: CGFloat(alpha)
  7. )
  8. }
  9. }
  10.  
  11. extension NSBezierPath {
  12.  
  13. var CGPath: CGPathRef {
  14. return self.toCGPath()
  15. }
  16.  
  17. /// Transforms the NSBezierPath into a CGPathRef
  18. ///
  19. /// :returns: The transformed NSBezierPath
  20. private func toCGPath() -> CGPathRef {
  21.  
  22. // Create path
  23. let path = CGPathCreateMutable()
  24. var points = UnsafeMutablePointer<NSPoint>.alloc(3)
  25. let numElements = self.elementCount
  26.  
  27. if numElements > 0 {
  28.  
  29. var didClosePath = true
  30.  
  31. for index in 0..<numElements {
  32.  
  33. let pathType = self.elementAtIndex(index,associatedPoints: points)
  34.  
  35. switch pathType {
  36.  
  37. case .MoveToBezierPathElement:
  38. CGPathMoveToPoint(path,nil,points[0].x,points[0].y)
  39. case .LineToBezierPathElement:
  40. CGPathAddLineToPoint(path,points[0].y)
  41. didClosePath = false
  42. case .CurveToBezierPathElement:
  43. CGPathAddCurveToPoint(path,points[0].y,points[1].x,points[1].y,points[2].x,points[2].y)
  44. didClosePath = false
  45. case .ClosePathBezierPathElement:
  46. CGPathCloseSubpath(path)
  47. didClosePath = true
  48. }
  49. }
  50.  
  51. if !didClosePath { CGPathCloseSubpath(path) }
  52. }
  53.  
  54. points.dealloc(3)
  55. return path
  56. }
  57. }
  58.  
  59. class RadioButton: NSButton {
  60.  
  61. private var containerLayer: CALayer!
  62. private var backgroundLayer: CALayer!
  63. private var dotLayer: CALayer!
  64. private var hoverLayer: CALayer!
  65.  
  66. required init?(coder: NSCoder) {
  67. super.init(coder: coder)
  68. self.setupLayers(radioButtonFrame: CGRectZero)
  69. }
  70.  
  71. override init(frame frameRect: NSRect) {
  72. super.init(frame: frameRect)
  73. let radioButtonFrame = CGRect(
  74. x: 0,y: 0,width: frameRect.height,height: frameRect.height
  75. )
  76. self.setupLayers(radioButtonFrame: radioButtonFrame)
  77. }
  78.  
  79. override func drawRect(dirtyRect: NSRect) {
  80. }
  81.  
  82. private func setupLayers(radioButtonFrame radioButtonFrame: CGRect) {
  83. //// Enable view layer
  84. self.wantsLayer = true
  85.  
  86. self.setupBackgroundLayer(radioButtonFrame)
  87. self.setupDotLayer(radioButtonFrame)
  88. self.setupHoverLayer(radioButtonFrame)
  89. self.setupContainerLayer(radioButtonFrame)
  90. }
  91.  
  92. private func setupContainerLayer(frame: CGRect) {
  93.  
  94. self.containerLayer = CALayer()
  95. self.containerLayer.frame = frame
  96. self.containerLayer.geometryFlipped = true
  97.  
  98. //// Mask
  99. let mask = CAShapeLayer()
  100. mask.path = NSBezierPath(ovalInRect: frame).CGPath
  101. mask.fillColor = NSColor.blackColor().CGColor
  102. self.containerLayer.mask = mask
  103.  
  104. self.containerLayer.addSublayer(self.backgroundLayer)
  105. self.containerLayer.addSublayer(self.dotLayer)
  106. self.containerLayer.addSublayer(self.hoverLayer)
  107.  
  108. self.layer!.addSublayer(self.containerLayer)
  109. }
  110.  
  111. private func setupBackgroundLayer(frame: CGRect) {
  112.  
  113. self.backgroundLayer = CALayer()
  114. self.backgroundLayer.frame = frame
  115. self.backgroundLayer.backgroundColor = NSColor.whiteColor().CGColor
  116. }
  117.  
  118. private func setupDotLayer(frame: CGRect) {
  119.  
  120. let dotFrame = frame.rectByInsetting(dx: 6,dy: 6)
  121. let maskFrame = CGRect(origin: CGPointZero,size: dotFrame.size)
  122.  
  123. self.dotLayer = CALayer()
  124. self.dotLayer.frame = dotFrame
  125. self.dotLayer.shadowColor = NSColor.colorWithDecimal(deviceRed: 46,deviceGreen: 146,deviceBlue: 255,alpha: 1.0).CGColor
  126. self.dotLayer.shadowOffset = CGSize(width: 0,height: 2)
  127. self.dotLayer.shadowOpacity = 0.4
  128. self.dotLayer.shadowRadius = 2.0
  129.  
  130. //// Mask
  131. let maskLayer = CAShapeLayer()
  132. maskLayer.path = NSBezierPath(ovalInRect: maskFrame).CGPath
  133. maskLayer.fillColor = NSColor.blackColor().CGColor
  134.  
  135. //// Gradient
  136. let gradientLayer = CAGradientLayer()
  137. gradientLayer.frame = CGRect(origin: CGPointZero,size: dotFrame.size)
  138. gradientLayer.colors = [
  139. NSColor.colorWithDecimal(deviceRed: 29,deviceGreen: 114,deviceBlue: 253,alpha: 1.0).CGColor,NSColor.colorWithDecimal(deviceRed: 59,deviceGreen: 154,alpha: 1.0).CGColor
  140. ]
  141. gradientLayer.mask = maskLayer
  142.  
  143. //// Inner Stroke
  144. let strokeLayer = CAShapeLayer()
  145. strokeLayer.path = NSBezierPath(ovalInRect: maskFrame.rectByInsetting(dx: 0.5,dy: 0.5)).CGPath
  146. strokeLayer.fillColor = NSColor.clearColor().CGColor
  147. strokeLayer.strokeColor = NSColor.blackColor().colorWithAlphaComponent(0.12).CGColor
  148. strokeLayer.lineWidth = 1.0
  149.  
  150. self.dotLayer.addSublayer(gradientLayer)
  151. self.dotLayer.addSublayer(strokeLayer)
  152. }
  153.  
  154. private func setupHoverLayer(frame: CGRect) {
  155.  
  156. self.hoverLayer = CALayer()
  157. self.hoverLayer.frame = frame
  158.  
  159. //// Inner Shadow
  160. let innerShadowLayer = CAShapeLayer()
  161. let ovalPath = NSBezierPath(ovalInRect: frame.rectByInsetting(dx: -10,dy: -10))
  162. let cutout = NSBezierPath(ovalInRect: frame.rectByInsetting(dx: -1,dy: -1)).bezierPathByReversingPath
  163. ovalPath.appendBezierPath(cutout)
  164. innerShadowLayer.path = ovalPath.CGPath
  165. innerShadowLayer.shadowColor = NSColor.blackColor().CGColor
  166. innerShadowLayer.shadowOpacity = 0.2
  167. innerShadowLayer.shadowRadius = 2.0
  168. innerShadowLayer.shadowOffset = CGSize(width: 0,height: 2)
  169.  
  170. self.hoverLayer.addSublayer(innerShadowLayer)
  171.  
  172. //// Inner Stroke
  173. let strokeLayer = CAShapeLayer()
  174. strokeLayer.path = NSBezierPath(ovalInRect: frame.rectByInsetting(dx: -0.5,dy: -0.5)).CGPath
  175. strokeLayer.fillColor = NSColor.clearColor().CGColor
  176. strokeLayer.strokeColor = NSColor.blackColor().colorWithAlphaComponent(0.22).CGColor
  177. strokeLayer.lineWidth = 2.0
  178.  
  179. self.hoverLayer.addSublayer(strokeLayer)
  180. }
  181. }
  182.  
  183. let rbFrame = NSRect(
  184. x: 87,y: 37,width: 26,height: 26
  185. )
  186.  
  187. let viewFrame = CGRect(
  188. x: 0,width: 200,height: 100
  189. )
  190.  
  191. let view = NSView(frame: viewFrame)
  192. view.wantsLayer = true
  193. view.layer!.backgroundColor = NSColor.colorWithDecimal(deviceRed: 40,deviceGreen: 40,deviceBlue: 40,alpha: 1.0).CGColor
  194.  
  195. let rb = RadioButton(frame: rbFrame)
  196. 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对我之前提到的问题的回答似乎在我们的案例中工作正常:

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

请看这里的差异:

另一个解决方案是利用角半径而不是贝塞尔路径来模拟一个圆圈,这次它是非常准确的:


>最后我假设游乐场和真正的OS X项目是苹果公司配置的Playground,以便一些优化被关闭,所以使用CAShapeLayer甚至想到的路径可以获得最佳的抗锯齿功能.毕竟你是原型,表演并不重要,特别是绘画操作.

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

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

还有@Bannings在这篇文章的另一个答案中说过.由于在不同的坐标系中渲染的事实,阴影是相反的.看到他的答案来解决这个问题.

猜你在找的Swift相关文章