NSBezierPath with rounded rect does not have smooth corners - swift

I'd like to create a rounded rect similar to the one around the labels (while editing) in AddressBook. I am trying to use a bezierPath with a rounded rect and then stroking it to do this. But, the end result doesn't have very smooth edges.
Address Book label
Address Book label zoomed in pixie
My label
My label zoomed in pixie
It looks like the curves in the addressbook version are more aggressively mixed towards the background white color. In Pixie, the pixel I have my cursor on has a sRGB value of (0.98, 0.98, 0.98), wheras in my version it is (0.86, 0.86, 0.86) resulting in a bit of jagged edge.
My code to draw the rect is
override func drawWithFrame(cellFrame: NSRect, inView controlView: NSView) {
if let context = NSGraphicsContext.currentContext() {
context.saveGraphicsState()
let borderColor = NSColor.init(SRGBRed: 0.75, green: 0.75, blue: 0.75, alpha: 1)
let outline = NSBezierPath.init(roundedRect: cellFrame, xRadius: 4, yRadius: 4)
outline.lineWidth = 4
borderColor.setStroke()
outline.stroke()
drawInteriorWithFrame(cellFrame, inView: controlView)
context.restoreGraphicsState()
}
}
I have tried playing around with different compositing types, line widths and xy radiuses for the round rect - without much success. I would appreciate some guidance on this. Thanks

I believe this article here explains the issue. When we stroke a rect, Coregraphics draws the outline along the middle of the edge of the rectangle we provide. If the rect is 1 pixel wide, then half the stroke line will lie outside the rectangle, while the other half will lie on the inside. As we can't draw half a pixel, Coregraphics mixes the two colors (on the inside and outside) to draw the line. So, to draw a single pixel line, we need to modify the outline rect
CGRect rectFor1PxStroke(CGRect rect)
{
return CGRectMake(rect.origin.x + 0.5, rect.origin.y + 0.5, rect.size.width - 1, rect.size.height - 1);
}
A couple of other options are also provided in the referenced article. But, with my initial testing, the above solution seems to work fine - along with setting the linewidth = 1 and x-y radiuses of the curve to 2

Related

swift CAShapeLayer strokeColor by gradient color

hi i have a circle shapeLayer need color by GradientColor
i tried ues CAGradientLayer and mask it like this
let gradientChargeLayer = CAGradientLayer()
gradientChargeLayer.colors = CGColor.mColor
gradientChargeLayer.frame = vShape.bounds
gradientChargeLayer.locations = [0,1]
gradientChargeLayer.startPoint = CGPoint(x: 0,y: 0.5)
gradientChargeLayer.endPoint = CGPoint(x: 1,y: 0.5)
gradientChargeLayer.mask = shapeLayer
vShape.layer.addSublayer(gradientChargeLayer)
but what i want is gradient color from stroke start to end
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor <<< here
thanks
you could’ve different approaches I believe
One, you could make an outer and inner circle and then add the required path/layer to the outer circle (this circle bigger than inner) both circles without visible stroke
Or I could recommend you UIGraphigsImageRenderer to work with
https://developer.apple.com/documentation/uikit/uigraphicsimagerenderer
This last have a context that could be used to do more advanced rendering context.cgContext.setStrokePattern here you can add a CGPattern and colors to trying accomplish it
PD. Sorry if my english is confuse
Shape layers only draw in a single color, as I guess you are aware.
By making your shape layer a mask onto a gradient layer you're able to have the shape layer reveal the color of the underlying gradient layer.
If you are always drawing a circle and want the color of the circle's stroke to vary from beginning to end you could set up your gradient layer using a type of conic, and specifying the colors in the gradient layer to begin and end where you want them. However, that would only work for shape layers that contain ovals, where the size and center of the shape and the size and the center of the gradient match.
As #Guillodacosta said in their answer, you may have to give up on using a shape layer and draw your graphic into a graphics context using Core Graphics. (You could do that once and capture the results into a UIImageView to avoid the slowdown of Core Graphics rendering.)

How to draw a line in a custom view (Retina) with a constant intensity?

I want to write a custom drawing view, which should take advantage of the Retina display. I am using a 2019 MacBook Pro. To test the drawing I am just drawing parallel lines with a width of 1px. But the lines show an uneven brightness across the view.
I already tried the conversion from screen coordinates as posted in
https://developer.apple.com/library/archive/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/APIs/APIs.html#//apple_ref/doc/uid/TP40012302-CH5-SW2
This is the relevant code block from the views draw(...) method
// fill with a black background
let background: NSBezierPath = NSBezierPath(rect: bounds)
NSColor.black.setFill()
background.fill()
// create a path that displays vertical lines 1px wide
let path: NSBezierPath = NSBezierPath()
let height: CGFloat = self.bounds.height
var xPosition: CGFloat = 0.0
while self.frame.width > xPosition {
let rect:NSRect = NSRect(x: xPosition, y: 0, width: 0.5, height: height)
path.appendRect(rect)
xPosition += 10.0
}
NSColor.yellow.setFill()
path.fill()
The result looks like this picture
the lines getting lighter und darker again, they should have all the same intensity
Okay, I figured out the solution. I was not aware, that an "unscaled" MacBook Pro Monitor that's set to default/unscaled is actually scaled. Apple upscales the screen resolution by the factor 1,125 as default. To see the correct screen you need to choose scaled and get one step down to see the nativ screen resolution. I am still wondering if there are optimisation measures possible to get a better result even on the scaled display other than using wider lines

How can I use CGAffineTransform to skew a UIView in multiple directions, for example make one side larger than the other?

I know how to rotate a UIView and also scale it and translate x and y coordinates. But I don't know how to skew it as shown in the images below.
First image is a normal red rectangle with margins of size 20px from the sides. This is the unskewed view.
Here is the desired end result rectangle. I have drawn arrows to emphasize the direction of change. Its height is larger on the right side, and lower on the left side, and it is also moved slightly towards the right, so it looks like the rectangle is being pushed into the screen from the left side.
How can I achieve this transform?
You could create a rotation in the Y-axis and adjust the m34 for your perspective:
// Assuming that you have a CALayer. It can be your view's layer.
// In my example this was a 100x100 square layer
let boxLayer = CALayer()
var transform = CATransform3DIdentity
let angle = -CGFloat.pi / 8
transform.m34 = -1.0 / 500.0 // [500]: Smaller -> Closer to the 'camera', more distorted
transform = CATransform3DRotate(transform, angle, 0, 1, 0)
boxLayer.transform = transform
which results to this:

Adjust text size to fit SKLabelNode with fixed width

New to Spritekit and trying to fix the width of a SKLabelNode and then adjust the font of text to fit within the node accordingly.
Have been looking through the docs but cant seem to find anything suitable, like the UILabel function:
A UILabel.adjustsFontSizeToFitWidth = true
Thanks
This is a nice function that I found a while back, but I can't remember exactly where
func adjustLabelFontSizeToFitRect(labelNode:SKLabelNode, rect:CGRect) {
// Determine the font scaling factor that should let the label text fit in the given rectangle.
let scalingFactor = min(rect.width / labelNode.frame.width, rect.height / labelNode.frame.height)
// Change the fontSize.
labelNode.fontSize *= scalingFactor
// Optionally move the SKLabelNode to the center of the rectangle.
labelNode.position = CGPoint(x: rect.midX, y: rect.midY - labelNode.frame.height / 2.0)
}
This adjusts the font size of the label to fit the width exactly, but you may want to change the function in order to add some extra padding on all sides.

Drawing multiple circles on a single line CGGraphicsContext

I have a custom class that manages a custom view that has a horizontal center on screen (which will represent a solid line). And I've drawn the line like this:
override func drawRect(rect: CGRect)
{
let line = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(line, 3.0)
CGContextSetStrokeColorWithColor(line, UIColor.redColor().CGColor)
CGContextMoveToPoint(line, 0, self.bounds.midY)
CGContextAddLineToPoint(line, self.bounds.width, self.bounds.midY)
CGContextStrokePath(line)
}
However, I need to draw a multiple solid circles on this line and try to look it like a point in a chart. I'm trying to make a mini chart representation in fact. How can I draw the circles? With a nested 'for in' loop or? Is there any official chart API from Apple?
UIGraphicsGetCurrentContext() gives you a context that you can draw multiple things into. Calling it line is not the right idea, because it can contain multiple lines, or circles, or all sorts of other things.
Think of the context as an artist sitting in front of a blank canvas. You give the artist instructions, like "draw a red line, then draw a blue circle". The artist follows the instructions, and afterwards, you look at the canvas.
Here's how you might draw a line and then a circle.
let context = UIGraphicsGetCurrentContext()
// Tell the context what stroked paths should look like
CGContextSetLineWidth(context, 3.0)
CGContextSetStrokeColorWithColor(context, UIColor.redColor().CGColor)
// Draw a single line
CGContextMoveToPoint(context, 0, self.bounds.midY)
CGContextAddLineToPoint(context, self.bounds.width, self.bounds.midY)
CGContextStrokePath(context)
// Now draw a circle by filling a path.
// First, set the fill color:
CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)
// Specify how big the circle is, and where its center is:
let circleRadius = CGFloat(5.0)
let circleCenter = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
// Then add a circle to the context, by specifying the rectangle that surrounds it:
let circleRect = CGRect(x: circleCenter.x - circleRadius,
y: circleCenter.y - circleRadius,
width: circleRadius * 2,
height: circleRadius * 2)
CGContextAddEllipseInRect(context, circleRect)
// And fill that circle:
CGContextFillPath(context)
If you want to draw more circles but in different places, just call CGContextAddEllipseInRect and CGContextFillPath again, but with different values for circleRect. Depending on what you want, a for loop might be appropriate. It's entirely up to you.
If you don't want to write it yourself, there are lots of 3rd-party chart libraries available, just do a search. Apple does not provide an "official" one.