macos swift cocoa nsview swift2
Aquí hay un zip que

macos - CustomView parece extraño en mi proyecto, pero bueno en el patio de recreo



swift cocoa (2)

Lo arreglé por reemplazar este código de línea:

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

con:

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

y reemplace innerShadowLayer.shadowOffset = CGSize(width: 0, height: 2) con:

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

Creo que obtendrás el mismo resultado así:

Izquierda = en el patio de recreo .
Derecha = en mi proyecto .

Parece que el Playground muestra la ruta Bezier en el sistema de coordenadas LLO, puede visitar el enlace: https://forums.developer.apple.com/message/39277#39277

Así que he creado un NSButton personalizado para tener un hermoso botón de NSButton , pero estoy experimentando un error muy extraño.

Mi botón de radio se ve bien en el patio de recreo, pero cuando lo agrego a mi proyecto, parece extraño.

Aquí hay capturas de pantalla:

Izquierda = en el patio de recreo .
Derecha = en mi proyecto .

Como puede ver, a la derecha (en mi proyecto), el punto azul se ve horrible , no es liso , lo mismo para el círculo blanco (es menos visible con el fondo oscuro).

En mi proyecto, NSShadow en mi CALayer también se voltea, incluso si la propiedad geometryFlipped en mi CALayer principal (_containerLayer_) se establece en true . -> CORREGIDO: ver respuesta @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)

Estoy usando exactamente el mismo código en mi proyecto y en el patio de recreo.
Aquí hay un zip que contiene el patio de recreo y el proyecto.

Para que quede claro: quiero saber por qué los dibujos circulares son suaves en el patio de recreo pero no en los proyectos. (Ver la respuesta de @Bannings, es más obvio con sus capturas de pantalla)


Tomó tiempo pero creo que finalmente descubrí todo o casi todo.

  1. Primero algo de ciencia: los círculos o arcos no se pueden representar a través de las curvas de Bézier. Esa es una propiedad de las curvas de Bézier como se indica aquí: https://en.wikipedia.org/wiki/Bézier_curve

    Por lo tanto, al usar NSBezierPath(ovalInRect:) , de hecho estás generando una curva de Bézier que se aproxima a un círculo. Esto puede conducir a una diferencia en apariencia y a cómo se renderiza la forma. Aún así, esto no debería ser un problema en nuestro caso porque la diferencia está entre dos curvas de Bézier (la de Playground y la de un proyecto de OS X real), pero aún así me parece interesante observar en caso de que creas que el círculo no es lo suficientemente perfecto .

  2. En segundo lugar, como se indica en esta pregunta (Cómo dibujar un círculo suave con CAShapeLayer y UIBezierPath), existen diferencias en la forma en que se aplicará el antialiasing a su ruta, dependiendo de dónde se usa la ruta. NSView''s drawRect: siendo el lugar donde el antialiasing de la ruta será el mejor y CAShapeLayer será el peor .

    También encontré que la documentación de CAShapeLayer tiene una nota que dice esto:

    La rasterización de formas puede favorecer la velocidad sobre la precisión. Por ejemplo, los píxeles con múltiples segmentos de ruta de intersección pueden no dar resultados exactos.

    La respuesta de Glen Low a la pregunta que mencioné anteriormente parece funcionar bien en nuestro caso:

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

    Vea las diferencias aquí:

    Otra solución es aprovechar los radios de esquina en lugar de los caminos de Bézier para simular un círculo, y esta vez es bastante preciso:

  3. Por último, mi suposición sobre la diferencia entre Playground y el proyecto real OS X es que Apple configuró Playground para que algunas optimizaciones se apaguen, por lo que la ruta, incluso pensada con CAShapeLayer obtiene el mejor anti-aliasing posible. Después de todo lo que está haciendo prototipos, las actuaciones no son realmente importantes, especialmente en las operaciones de dibujo.

    No estoy seguro de estar en lo cierto, pero creo que no sería sorprendente. Si alguien tiene alguna fuente, felizmente la agregaré.

Para mí, la mejor solución si realmente necesitas el mejor círculo posible es usar radios de esquina.

También según lo indicado por @Bannings en otra respuesta a esta publicación. Las sombras se invierten debido a que el patio de recreo se renderiza en un sistema de coordenadas diferente. Vea su respuesta para arreglar esto.