Custom NSView draws over controls on top of it - objective-c

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.

Related

Rounded NSView in a Transparent Window

I'm trying to make a transparent NSWindow with a rounded view in there.
I'm trying to have a rounded view with a transparent window.
This is what it looks like now: (see the little dots in the corners)
Here's another example with the border radius set to 10px (set in NSView drawRect):
I am using code from this Apple sample: https://developer.apple.com/library/mac/#samplecode/RoundTransparentWindow/Introduction/Intro.html
Specifically this method in my NSWindow subclass:
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)aStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)flag {
// Using NSBorderlessWindowMask results in a window without a title bar.
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
if (self != nil) {
// Start with no transparency for all drawing into the window
[self setAlphaValue:1.0];
// Turn off opacity so that the parts of the window that are not drawn into are transparent.
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
}
return self;
}
And this in my NSView subclass:
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor redColor] set];
NSBezierPath* thePath = [NSBezierPath bezierPath];
[thePath appendBezierPathWithRoundedRect:dirtyRect xRadius:3 yRadius:3];
[thePath fill];
}
Can anyone tell me what I'm missing here?
Thanks.
Are you looking for something like the following, where there's a red outline (stroke), but the center area is transparent?
If so, to achieve that, I used the following code:
- (void)drawRect:(NSRect)frame {
frame = NSInsetRect(self.frame, 3.0, 3.0);
[NSBezierPath setDefaultLineWidth:6.0];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:frame
xRadius:6.0 yRadius:6.0];
[[NSColor redColor] set];
[path stroke];
}
If that's what you're looking for, you can probably use that as a starting point. You'll want to make sure that you inset the frame rect one half of the stroke line width, so as to avoid the problem with clipping the corners like you were seeing.
Not sure if this is what you are looking for but there is a great class by Matt Gemmell called MAAttachedWindow and can be found here: http://mattgemmell.com/2007/10/03/maattachedwindow-nswindow-subclass/
It's a little older but still works great for me when I need to do a 'floating' popup window and configure transparency, border radii, and even add a small arrow for context if desired. I use it all the 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).

How to draw over a subview of NSView

I'm trying to draw at the top of my NSView which has some subviews.
In fact I'm trying to reproduce the connection line style of Interface Builder. Here is the code I'm using for the moment:
- (void)drawRect:(CGRect)dirtyRect
{
// Background color
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
// Draw line
if(_connecting)
{
CGContextRef c = [[NSGraphicsContext currentContext] graphicsPort];
[[NSColor redColor] setStroke];
CGContextMoveToPoint(c, _start.x, _start.y);
CGContextAddLineToPoint(c, _end.x, _end.y);
CGContextSetLineWidth(c, LINE_WIDTH);
CGContextClosePath(c);
CGContextStrokePath(c);
}
}
The first part is to color my NSView (if you know an other way, tell me please 'cause I come from iPhone development and I miss the backgroundColor property of UIView)
Then if a connection if detected, I draw it with 2 NSPoints. This code works but I didn't get it to draw over subviews, only on the first NSView.
A parent view cannot draw over its subviews. You would have to place another view over the subviews and draw the line there.

Redrawing NSCollectionView on Scroll Causes Breakup of Graphics

I have a minor irritant in an NSCollectionView in which the NSCollectionViewItem's break up visually when I scroll the window.
The rate of breakup depends upon the rate of scrolling. For example, if I scroll slowly the breakup occurs more often. Fast scrolling less so. It appears that the problem is when the custom NSView of the NSCollectionViewItem I am using crosses the boundary of the viewable frame.
My NSView (custom view of the NSCollectionViewItem) has a very simple drawing algorithm - nothing too complex.
Essentially I create a frame within the dirtyRect of the drawRect method and create a few frames inside that:
-(void)drawRect:(NSRect)dirtyRect
{
NSRect mOuterFrame = NSMakeRect(dirtyRect.origin.x, dirtyRect.origin.y, 104, 94);
NSRect mSelectedFrame = NSInsetRect(mOuterFrame, 2, 2);
NSRect mRedFrame = NSInsetRect(mSelectedFrame, 2, 2);
NSRect mInnerFrame = NSInsetRect(mRedFrame, 2, 2);
NSBezierPath * mOuterFramePath = [NSBezierPath bezierPathWithRect:mOuterFrame];
NSBezierPath * mSelectedFramePath = [NSBezierPath bezierPathWithRect:mSelectedFrame];
NSBezierPath * mRedFramePath = [NSBezierPath bezierPathWithRect:mRedFrame];
NSBezierPath * mInnerFramePath = [NSBezierPath bezierPathWithRect:mInnerFrame];
[mainBackgroundColor set];
[mOuterFramePath fill];
if (selected)
[[NSColor yellowColor] set];
else
[mainBackgroundColor set];
[mSelectedFramePath fill];
if (isRedBorder)
[[NSColor redColor] set];
else
[mainBackgroundColor set];
[mRedFramePath fill];
[[NSColor blackColor] set];
[mInnerFramePath fill];
}
I have tried locking the focus and releasing before and after the code as well as setting the graphics context and restoring it - none of which seem to solve the problem.
I am using Snow Leopard - not that I think that makes a difference.
Solution Update
For anyone interested here is the solution to the problem as recommended by NSResponder. I was creating the initial mOuterFrame based upon the drawRect: methods dirtyRect which, as pointed out, was the incorrect thing to do. A quick change from:
NSRect mOuterFrame = NSMakeRect(dirtyRect.origin.x, dirtyRect.origin.y, 104, 94);
To a 0 based origination point:
NSRect mOuterFrame = NSMakeRect(0, 0, 104, 94);
I also tweaked the efficiency of the code, being as I am using only rectangles, as suggested although the code change above was enough to resolve the problem in itself. I had to add a change to get a two pixel line. New method:
-(void)drawRect:(NSRect)dirtyRect
{
NSRect mOuterFrame = NSMakeRect(0, 0, 104, 94);
NSRect mSelectedFrame = NSInsetRect(mOuterFrame, 2, 2);
NSRect mRedFrame = NSInsetRect(mSelectedFrame, 2, 2);
NSRect mInnerFrame = NSInsetRect(mRedFrame, 2, 2);
[NSBezierPath setDefaultLineWidth:2.0];
[mainBackgroundColor set];
[NSBezierPath strokeRect:mOuterFrame];
if (selected)
[[NSColor yellowColor] set];
else
[mainBackgroundColor set];
[NSBezierPath strokeRect:mSelectedFrame];
if (isRedBorder)
[[NSColor redColor] set];
else
[mainBackgroundColor set];
[NSBezierPath strokeRect:mRedFrame];
[[NSColor blackColor] set];
[NSBezierPath strokeRect:mInnerFrame];
}
First thing I noticed is that your code above is rather wasteful, since you're creating a bunch of paths when you could just be using NSFrameRect() and NSRectFill() instead.
Secondly, the geometry of what you draw shouldn't be dependent on the rectangle passed to -drawRect:. That rectangle just tells you what the area to update is, so you can be more efficient in deciding what to draw if your view is complex.

Create a colored bubble/circle programmatically in ObjectiveC and Cocoa

Can anyone guide me in the correct way to build a colored bubble/circle programmatically?
I can't use images as I need it to be able to be any color depending on user interaction.
My thought was maybe to make a white circle image and then overlay a color on top of it.
However I am not sure if this would work, or how to really go about it.
If someone could point me the right direction I would appreciate it.
There are a couple steps to drawing something in Cocoa.
First you need a path that will be used to define the object that you are going to be drawing. Take a look here Drawing Fundamental Shapes for a guide on creating paths in Cocoa. You will be most interested in sending the "appendBezierPathWithOvalInRect" message to an "NSBezierPath" object, this takes a rectangle that bounds the circle you want to draw.
This code will create a 10x10 circle at coordinates 10,10:
NSRect rect = NSMakeRect(10, 10, 10, 10);
NSBezierPath* circlePath = [NSBezierPath bezierPath];
[circlePath appendBezierPathWithOvalInRect: rect];
Once you have your path you want to set the color for the current drawing context. There are two colors, stroke and fill; stroke is the outline of the path and the fill is the interior color. To set a color you send "set" to an "NSColor" object.
This sets the stroke to black and the fill to red:
[[NSColor blackColor] setStroke];
[[NSColor redColor] setFill];
Now that you have your path and you have your colors set just fill the path and then draw it:
[path stroke];
[path fill];
All of this will need to be done in a graphics context like in drawRect of a view perhaps. All of this together with a graphics context would look like this:
- (void)drawRect:(NSRect)rect
{
// Get the graphics context that we are currently executing under
NSGraphicsContext* gc = [NSGraphicsContext currentContext];
// Save the current graphics context settings
[gc saveGraphicsState];
// Set the color in the current graphics context for future draw operations
[[NSColor blackColor] setStroke];
[[NSColor redColor] setFill];
// Create our circle path
NSRect rect = NSMakeRect(10, 10, 10, 10);
NSBezierPath* circlePath = [NSBezierPath bezierPath];
[circlePath appendBezierPathWithOvalInRect: rect];
// Outline and fill the path
[circlePath stroke];
[circlePath fill];
// Restore the context to what it was before we messed with it
[gc restoreGraphicsState];
}
You may use simple UIView to create perfect circle with only parameter radius:
// Add framework CoreGraphics.framework
#import <QuartzCore/QuartzCore.h>
-(UIView *)circleWithColor:(UIColor *)color radius:(int)radius {
UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 2 * radius, 2 * radius)];
circle.backgroundColor = color;
circle.layer.cornerRadius = radius;
circle.layer.masksToBounds = YES;
return circle;
}
Create an NSView subclass that holds an NSColor as an ivar. In the drawRect method, create an NSBezierPath of the appropriate size, using the view's bounds. Then set the color [myColor set] and fill the path [myPath fill]. There's a lot more you can do, such as set transparency, a border, and so on and so on, but I'll leave that to the docs unless you have a specific question.
To use the NSView subclass, just drag a view object onto your nib, and choose the name of your subclass in custom class in IB's inspector. You'll need to also set an outlet to it in your controller, so you can change the color as needed.
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(c, 40, 0, 255, 0.1);
CGContextSetRGBStrokeColor(c, 0, 40, 255, 0.5);
// Draw a green solid circle
CGContextSetRGBFillColor(c, 0, 255, 0, 1);
CGContextFillEllipseInRect(c, CGRectMake(100, 100, 25, 25));
Download sketch from apple. http://developer.apple.com/library/mac/#samplecode/Sketch
It can do a lot more, but one of the things is draw circles.