How to deal with drawRect in cocoa? - objective-c

I have to draw lines few times. I do this now:
- (void)drawRect:(NSRect)dirtyRect
{
[NSGraphicsContext saveGraphicsState];
NSBezierPath *path = [NSBezierPath bezierPath];
[path moveToPoint:point];
[[NSColor blackColor] set];
[path lineToPoint:point2];
[path stroke];
[NSGraphicsContext restoreGraphicsState];
}
And i call this from other class many time with other parameters:
[workspace setPoint1:someValue setPoint2:someOtherValue];
[workspace setNeedsDisplay:YES];
What i need?
I call this few times with changing parameters someValue and someOtherValue and I have to draw all lines and I want to see it. Now i see only last path. Where is the problem? How can i do this correctly?
Thank you.

If you want to draw more than one line, you'll need to... well... draw more than one line.
You'll need to come up with some way of storing all of the line segments that you want to draw, not just the most recent one. (Use NSMutableArray for this.) Once you've got that, you'll need to loop through that array and draw each line.

Related

CAShapeLayer keeps closing path, I want the path open

I'm using CA to draw line segments from an array of points. For some reason, although I did not close the NSBezierPath, CAShapeLayer results in a closed shape. The following are my codes. Do anyone else have this problem?
// mappedPoints is an array of CGPoints
NSBezierPath *path = [NSBezierPath bezierPath];
[path appendBezierPathWithPoints:mappedPoints count:numPoints];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [color CGColor];
shapeLayer.lineWidth = lineWidth;
shapeLayer.fillColor = nil;
[self.layer addSublayer:shapeLayer];
I'm allowing myself to guess here that the [path CGPath] call is what's closing that path for you, as that's exactly what happened to me.
To give a bit more context - Apple's Creating a CGPathRef From an NSBezierPath Object sample code, which creates an NSBezierPath category method like CGPath above, has this in it:
// Be sure the path is closed or Quartz may not do valid hit detection.
if (!didClosePath)
CGPathCloseSubpath(path);
The reasoning behind that (from the same source):
Quartz requires paths to be closed in order to do hit detection on the
path’s fill area
If you're not interested in fill-area hit detection, it's safe to skip over that path-closing part and you'd get the expected behavior.

Cocoa editing NSBezierPath

I have a simple bezierPath with 2 elements in a NSView;
I want to modify the last element (NSPoint) on a button pressed but my code don't have any visual effect on the path.
Here my code in NSView subclass:
NSBezierPath *path;
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
path = [NSBezierPath bezierPath];
[path moveToPoint:NSMakePoint(0, 0)];
[path lineToPoint:NSMakePoint(60, 60)];
[path setLineWith:2.0];
[[NSColor redColor] set];
[path stroke];
//the path is correctly drawing and visible
}
- (IBAction)buttonPressed:(id)sender {
NSPoint newPoint = NSMakePoint(120, 120);
[path setAssociatedPoints:&newPoint atIndex:1]; //has no visible effect
}
any suggestion ?
Every time you call drawRect: you are creating a new path and drawing it. Then, on the button press you modify the path.
So you have 2 problems:
You keep recreating the path - just create it once when the view is created
You don't redraw the view when the path is updated - use setNeedsDisplay
You are recreating the Bezier path each time through your -drawRect: method. So, it doesn't matter that you kept the last one and modified it. You are discarding that and creating a new one the next time your view draws.
Also, if your variable is really just declared outside of any curly braces ({ … }), then it's not an instance variable. It's just a file-scope global variable. That means it's shared by all instances of this view class.

Custom NSView draws over controls on top of it

I have an NSView with a custom subclass that draws a grid of rounded rectangles inside it. This NSView was placed with interface builder and on top of it I have some NSButtons.
The problem is that sometimes when the view is re-drawn (ie, when i click a button on top of it) then it re-draws over some of the buttons that are meant to stay on top. When this happens only the smaller rounded rects appear over the buttons though, not the background one that is drawn before the loop.
Here is the code form drawRect:
[NSGraphicsContext saveGraphicsState];
NSBezierPath *path = [NSBezierPath bezierPathWithRect:self.bounds];
[[NSColor grayColor] set];
[path fill];
[NSGraphicsContext restoreGraphicsState];
for( int r = 0; r < 15; r++ ){
for( int c = 0; c < 15; c++ ) {
[NSGraphicsContext saveGraphicsState];
// Draw shape
NSRect rect = NSMakeRect(20 * c, 20 * r, 15, 15);
NSBezierPath *roundedRect = [NSBezierPath bezierPathWithRoundedRect: rect xRadius:1 yRadius:1];
[roundedRect setClip];
// Fill
[[NSColor colorWithCalibratedHue:0 saturation:0 brightness:0.3 alpha:1] set];
[roundedRect fill];
// Stroke
[[NSColor colorWithCalibratedHue:0 saturation:0 brightness:0.5 alpha:1] set];
[roundedRect setLineWidth:2.0];
[roundedRect stroke];
[NSGraphicsContext restoreGraphicsState];
}
}
Here's a screenshot:
Update: Simplified the code, added a screenshot.
the mac has issues with overlapping sibling views. it didn't work before.... 10.6 and it still doesnt work quite often.
use a proper superview / subview hierachy
OK I just managed to solve this by removing the setClip and finding a different way to draw the inner stroke.
I'm sure it's possible to solve this while still using setClip but this solution worked fine for me this time.

Proper place to put NSTextField drawing code?

I am working on creating some custom Cocoa components. Currently I'm trying to figure out how to draw custom NSTextFields.
I have overridden the drawRect method on my subclass but when i start typing, i get a double rectangle like this http://imgur.com/a/LpUMy.
Here is my drawRect method
- (void)drawRect:(NSRect)dirtyRect
{
[NSGraphicsContext saveGraphicsState];
[[NSBezierPath bezierPathWithRoundedRect:dirtyRect xRadius:5.0f yRadius:5.0f] setClip];
[[NSColor grayColor] setFill];
NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver);
[NSGraphicsContext restoreGraphicsState];
[NSGraphicsContext saveGraphicsState];
NSRect rect = NSInsetRect(dirtyRect, 1.0f, 1.0f);
[[NSBezierPath bezierPathWithRoundedRect:rect xRadius:5.0f yRadius:5.0f] setClip];
[[NSColor whiteColor] setFill];
NSRectFillUsingOperation(rect, NSCompositeSourceOver);
[NSGraphicsContext restoreGraphicsState];
}
UPDATE:
I moved my drawing code into a NSTextFieldCell subclass as so
- (void)drawWithFrame:(NSRect)frame inView:(NSView *)controlView {
[NSGraphicsContext saveGraphicsState];
[[NSBezierPath bezierPathWithRoundedRect:frame xRadius:5.0f yRadius:5.0f] setClip];
[[NSColor grayColor] setFill];
NSRectFillUsingOperation(frame, NSCompositeSourceOver);
[NSGraphicsContext restoreGraphicsState];
[NSGraphicsContext saveGraphicsState];
[[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 1.0f, 1.0f) xRadius:3.0f yRadius:3.0f] setClip];
[[NSColor whiteColor] setFill];
NSRectFillUsingOperation(frame, NSCompositeSourceOver);
[NSGraphicsContext restoreGraphicsState];
}
But as soon as you are done editing it draws over the text, even though the cursor is still there? Any suggestions? I've tried drawing the title but it still happens.
Thanks for your help.
Answer:
NSCell Custom Highlight
By calling super drawInteriorWithFrame:inView I was able to stop the weird text disappearing issues.
It looks to me like you've ended up drawing inside the drawing of your superclass's (NSTextField's) drawRect: implementation. You haven't called super but it still manages to draw itself. I'm not sure why myself, but some NSControls such as text fields and buttons, when subclassed, will draw themselves regardless of whether or not you call drawRect: on them. For example, if you subclass a plain NSButton, implement drawRect: and don't call super, it'll draw the button anyways. Potentially over whatever you drew, which has caused confusion in the past. The easiest solution is to not subclass NSTextField, and see if there's another class you can subclass (like NSTextFieldCell mentioned in the comment).

Core graphics - Seams show on diagonally joined sides

I'm having a problem in Core Graphics where two shapes that are supposed to fit together are showing seams between them when their sides are diagonal. This is causing problems. I can 'fix' this by overlapping the shapes. This works for simple stuff, but when the shape is more complex things start to fall apart.
Here is a simple example:
[[UIColor blackColor] setFill];
[path fill]; //fills the entire background black
UIBezierPath *path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(0.0f, 0.0f)];
[path1 addLineToPoint:CGPointMake(100, 0.0f)];
[path1 addLineToPoint:CGPointMake(50, rect.size.height)];
[path1 addLineToPoint:CGPointMake(0, rect.size.height)];
[path1 closePath];
UIBezierPath *path2 = [UIBezierPath bezierPath];
[path2 moveToPoint:CGPointMake(100, 0)];
[path2 addLineToPoint:CGPointMake(rect.size.width, 0)];
[path2 addLineToPoint:CGPointMake(rect.size.width, rect.size.height)];
[path2 addLineToPoint:CGPointMake(50, rect.size.height)];
[path2 closePath];
[[UIColor lightGrayColor] setFill];
[path1 fill];
[path2 fill];
This produces a seam that looks something like this:
This is exceptionally problematic because this is occurring in a CGPathApply call back function I'm using to process shapes (I divide the shapes up, do some processing and put them back together again). Because of this, it's very difficult to determine how those shapes should be shifted in such a way as to remove the seams (by overlapping) without distorting the shape. Especially since some of these shapes are rather complex (not simple rects).
I did discover that turning off antialiasing (CGContextSetShouldAntialias(context, NO);) does remove the seems but things just get ugly if I do that, so it's not really an option.
Any ideas?