Redrawing NSCollectionView on Scroll Causes Breakup of Graphics - objective-c

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.

Related

Colorize shape "part by part"

I'd like to colorize a shape, for example a rectangle, part by part. For example, if I click on a button, I'd like to colorize a part of the rectangle and add the "amount" again until the form is full.
Example, before and after click:
Are there any possibilities?
This how you draw the second picture:
[[NSColor redColor]set];
NSRect rect1 = NSMakeRect(0, 0, 40, 40);
NSBezierPath* rectangle = [NSBezierPath bezierPathWithRect:rect1];
[rectangle setLineWidth:3.0];
[[NSColor redColor] setStroke];
[rectangle stroke];
NSRect rect = NSMakeRect(0, 0, 40, 20);
[[NSBezierPath bezierPathWithRect:rect] fill];
to draw just the first part, use just the top part of the code with no fill.
Edit: this is for OSX app...for iOS you're using CGRect, UIColor etc.

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).

How to make NSView's background image not repeat?

I want replicate the effect obtained by the following CSS code:
background: white url(./img/background.png) no-repeat;
I've written a subclass of NSView and override drawRect in this way:
- (void)drawRect:(NSRect)dirtyRect
{
dirtyRect = [self bounds];
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
[[NSColor colorWithPatternImage:[NSImage imageNamed:#"background.png"]] setFill];
NSRectFill(dirtyRect);
}
(I apologize for my bad english)
Take a look at NSImage class reference. Image can be drawn with drawInRect:fromRect:operation:fraction: and also with drawAtPoint:fromRect:operation:fraction:.
So You can Use this:
[[NSImage imageNamed:#"background.png"] drawInRect:dirtyRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1]; // Passing NSZeroRect causes the entire image to draw.
Instead this:
[[NSColor colorWithPatternImage:[NSImage imageNamed:#"background.png"]] setFill];
NSRectFill(dirtyRect);
Just use NSImage drawInRect:fromRect:operation:fraction: to draw your image in the view instead of filling your rect with a pattern color.

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.