I'm having trouble getting the NSRect of a view's border. I keep getting an unrecognized selector error.
Here is the code:
NSGradient *BorderGradient = [[NSGradient alloc] initWithStartingColor:[NSColor blackColor] endingColor:[NSColor whiteColor]];
[BorderGradient drawInRect:[self.window.contentView borderRect] angle:-90];
// Unrecognized Selector error here
I'm trying to access the border rect to add a color gradient to the border. The code also crashes when I try to access the borderRect by itself, like this:
NSRect rect = [self.window.contentView borderRect];
NSLog(#"origin.x = %f", rect.origin.x);
And if trying to convert the drawInRect: to CGRect. Like so:
NSGradient *BorderGradient = [[NSGradient alloc] initWithStartingColor:[NSColor blackColor] endingColor:[NSColor whiteColor]];
[BorderGradient drawInRect:NSRectToCGRect([self.window.contentView borderRect]) angle:-90];
// Unrecognized Selector error here
Any help would be greatly appreciated.
Thanks!
NSView doesn't have a method named -borderRect. Is it a custom method you've implemented? NSBox does have a -borderRect method. Are you expecting your window's content view to be an instance of NSBox? Have you checked to see that that's actually true.
Perhaps you really want -frame or -bounds?
NSView (what is returned from self.window.contentView) does not have a borderRect method, which is causing the Unrecognized Selector error.
You probably want frame or bounds instead.
Related
I have an NSImage in an NSPanel in which I'd like to get the mouse X, Y co-ords.
If I set the NSImage layer content withlayer.content =splitNSImage (where splitNSImage is an NSImage created from a URL)and then
NSRect layerBounds = [layer bounds];
NSTrackingAreaOptions options = (NSTrackingActiveAlways|NStrackingInVisibleRect|NSTrackingMouseEnter|
NSTrackingMouseExit|NSTrackingMouseMoved);
NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:layerBounds owner:self userinfo:nil];
[[layer contents] addTrackingArea:trackingArea];
I get a runtime error "Thread 1: "-[NSImage addTrackingArea:]: Unrecognised selector sent to instance 0x600003335400" on the last line.
If I use the use [NSPanel contentView] instead of [layer contents] it works OK but gives the mouse co-ords (in the MouseMoved event) in the panel rather than the imageView
I have a UIControl subclass where I have drawRect implemented. I am trying to draw a rectangle inside of the drawRect method using a UIBezierPath. All the appropriate methods are being called, nothing is null and the app runs with no issues. The problem is, nothing but the frame is drawn. Meaning, I can see the area of the frame, because it is black, but I cannot see anything that was drawn onto the frame.
Here is what I am doing to instantiate the class:
ZHButton *button =
[[ZHButton alloc] initWithFrame:CGRectMake(100, 200, 100, 50)];
button.buttonType = NSSelectorFromString(LEFT_ROUNDED_CORNER_BUTTON);
[self.view addSubview:button];
Here is the drawRect: method, which calls different methods using a selector:
- (void)drawRect:(CGRect)rect {
UIBezierPath *button = [self performSelector:self.buttonType withObject:nil];
[[UIColor orangeColor] setFill];
[button fill];
}
- (UIBezierPath *)drawButtonWithLeftCornersRounded {
return [UIBezierPath bezierPathWithRoundedRect:self.frame
byRoundingCorners:UIRectCornerTopLeft |
UIRectCornerBottomLeft
cornerRadii:buttonRadii];
}
Everything appears to be working fine, but the UIBezierPath does not get drawn. Also, not sure if using a selector is the best way to do this. Anyone know what is going on with this?
It seems you're not calling the drawButtonWithLeftCornersRounded method. I believe this line is wrong:
[self performSelector:self.buttonType withObject:nil];
But it should be:
[self performSelector:#selector(drawButtonWithLeftCornersRounded) withObject:nil];
Hope this helps!
Solved it. In drawButtonWithLeftCornersRounded bezierPathWithRoundedRect should not be self.frame. This will cause the x and y coordinates to be at the x, y location within the frame, not the view. I changed it the parameter to this:
CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
I am drawing on a custom view an NSGradient like this:
- (void)drawRect: (NSRect)dirtyRect
{
NSGradient* g = [[NSGradient alloc] initWithStartingColor: _color endingColor: [NSColor clearColor]];
[g drawInRect: [self bounds] angle: 90];
}
If _color is a normal color, for example [NSColor blueColor], everything is fine. The problem is that _color comes from a pattern image (which in this case is mostly grey with some fancy pixels on it), and when that happens the program keeps logging this error:
*** -[NSGradient initWithColors:atLocations:colorSpace:]: the color NSPatternColorSpace CGImageSource=0x400367d60"
)> cannot be converted into color space Generic RGB colorspace
_color = [NSColor colorWithPatternImage: [NSImage imageNamed: #"mainBG.png"]]
The image is completely opaque and is a png file.
Any ideas? perhaps I should change the file type? I don't know...
EDIT:
If I define _color like this:
_color = [[NSColor colorWithPatternImage: [NSImage imageNamed: #"mainBG.tiff"]] colorUsingColorSpace: [NSColorSpace genericRGBColorSpace]]
then no gradient is displayed. Nothing. Just as if I didn't have the drawRect: method. Any ideas?
An NSGradient doesn't work with a pattern as one of the colors. That's what the exception is telling you. It will only work with an NSColor that can be converted to an RGB color. If you want to make your pattern fade to clear, you'll have to do it another way.
I am drawing a custom window by setting a custom content view for the window. When I draw the custom view I give it rounded corners and a nice outline to mimic a proper window.
However, I see another 1 px outline around the window which strays from the edge at the corners. I have found that if I turn off the shadow it goes away, but obviously as this wants to act like a window I need the shadow. Here's what I mean about the 1px outline:
How can I prevent this?
EDIT
Code for drawing the custom window's content view:
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[self bounds] cornerRadius:5];
NSGradient* aGradient = [[[NSGradient alloc] initWithColorsAndLocations:
[NSColor colorWithDeviceRed:0.5569 green:0.5137 blue:0.4588 alpha:1.0000], 0.0,
[NSColor colorWithDeviceRed:0.5569 green:0.5137 blue:0.4588 alpha:1.0000], 1.0,
nil] autorelease];
[aGradient drawInBezierPath:path angle:90];
[path setLineWidth:4];
[[NSColor colorWithDeviceRed:0.4235 green:0.3922 blue:0.3451 alpha:0.9000] setStroke];
[path strokeInside];
[path setLineWidth:3];
[[NSColor colorWithDeviceRed:0.8431 green:0.8314 blue:0.8078 alpha:1.0000] setStroke];
[path strokeInside];
[path setLineWidth:1];
[[NSColor colorWithDeviceRed:0.4235 green:0.3922 blue:0.3451 alpha:0.9000] setStroke];
[path strokeInside];
Don't ask me how I got this, but this will solve your problem.
Define a category for NSWindow with the following content:
#implementation NSWindow(NoShadowRim)
- (id)_shadowRimInfo {
return #{
#"kCUIMeasureWindowFrameRimDensity": [NSNumber numberWithInt:0]
};
}
#end
DISCLAIMER: This overrides the internal method of NSWindow, so use it at your own risk. It may break with any OS X update.
You need to tell the window to recompute its shadow by sending it -invalidateShadow.
Try:
[[self window] display];
[[self window] setHasShadow:NO];
[[self window] setHasShadow:YES];
This line contouring the window area is drawn automatically. I have a window which has this line running accurately around bottom rounded corners. You have to setup the window as non-opaque and the background color to transparent:
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
The somewhere in the contentView -drawRect: you do
[NSGraphicsContext saveGraphicsState];
[pathWithBottomRoundedCorner addClip];
// your drawing here...
[NSGraphicsContext restoreGraphicsState];
That should work.
As I understand correctly, shadows are drawn by windows server. When you draw custom NSWindow with rounded corners or other not rectangular shapes, window server don't count those transparent pixels and dont drop shadow under them.
I developed some hack to avoid such behavior. Just drop additional shadow under your path, something like this:
NSShadow *headShadow = [[NSShadow alloc] init];
[headShadow setShadowColor:[NSColor colorWithSRGBRed:0.0
green:0.0
blue:0.0
alpha:0.16]];
[headShadow setShadowBlurRadius:0.0f];
[headShadow setShadowOffset:NSMakeSize(0.0f, 0.0f)];
[headShadow set];
Ideally for perfect result i fink shadow must be equal to window servers.
I'd like to change the fill of a button that I've drawn (I subclassed NSButton)
Here's the code I've got already:
- (void)drawRect:(NSRect)dirtyRect {
// Drawing code here.
// Create the Gradient
NSGradient *fillGradient = [[NSGradient alloc] initWithStartingColor:[NSColor lightGrayColor] endingColor:[NSColor darkGrayColor]];
// Create the path
aPath = [NSBezierPath bezierPath];
[aPath moveToPoint:NSMakePoint(10.0, 0.0)];
[aPath lineToPoint:NSMakePoint(85.0, 0.0)];
[aPath lineToPoint:NSMakePoint(85.0, 20.0)];
[aPath lineToPoint:NSMakePoint(10.0, 20.0)];
[aPath lineToPoint:NSMakePoint(0.0, 10.0)];
[aPath lineToPoint:NSMakePoint(10.0, 0.0)];
[fillGradient drawInBezierPath:aPath angle:90.0];
[fillGradient release];
}
- (void)mouseDown:(NSEvent *)theEvent {
NSGradient *fillGradient = [[NSGradient alloc] initWithStartingColor:[NSColor lightGrayColor] endingColor:[NSColor darkGrayColor]];
[fillGradient drawInBezierPath:aPath angle:-90.0];
}
and I get a EXC_BAD_ACCESS signal. How would I do this ?
The reason for the EXC_BAD_ACCESS is that the following line:
aPath = [NSBezierPath bezierPath];
creates an autoreleased bezierPath which will be released at the end of the current iteration of the run loop. To avoid the error, you'd need to change it to:
aPath = [[NSBezierPath bezierPath] retain];
However, you're approaching the problem from the wrong direction. Drawing should only be done in the -drawRect: method (or methods which are only called from -drawRect:). Instead of trying to draw in your mouseDown: method, you should create a new BOOL instance variable for your class (called, for instance, mouseIsDown) and set that in mouseDown:. Then use that boolean to determine how to fill the button:
- (void)drawRect:(NSRect)aRect {
NSGradient *fillGradient = nil;
if (mouseIsDown)
fillGradient = [[NSGradient alloc] initWithStartingColor:[NSColor lightGrayColor] endingColor:[NSColor darkGrayColor]];
else
fillGradient = [[NSGradient alloc] initWithStartingColor:[NSColor lightGrayColor] endingColor:[NSColor darkGrayColor]];
// Do the rest of your drawRect method
}
- (void)mouseDown:(NSEvent *)theEvent {
mouseIsDown = YES;
[self setNeedsDisplay:YES];
}
- (void)mouseUp:(NSEvent *)theEvent {
mouseIsDown = NO;
[self setNeedsDisplay:YES];
}
This isn't directly related to your question, but your question's title belies a false assumption that I want to address.
Changing the fill to a already drawn NSBezierPath
That's not possible.
In Cocoa, “fill” is a verb, not a property of an object like it is in Illustrator or Lineform. You do not set the fill of a path, nor change it later; you fill the path, and what you change by this is pixels in some unseen backing store. The real-world analogy would be setting up splines on a flat pane of glass, then filling the area bounded by the splines with paint and letting it dry. The “fill” is not a property of the splines; it's the act of pouring paint into the shape they define.
As with the analogy, you cannot remove or alter a fill you have previously done—the paint is stuck to the glass; there's no getting it off*. The only way to accomplish that effect is to re-do the fill with some other color. You can move the splines (create a new path) or add splines (add an intersecting subpath to the existing path) before filling, if you want to only change part of the existing drawing.
All of that applies to all drawing operations, including stroking paths and drawing raster images. Text drawing, too, as that's just another case of filling and/or stroking a path.
*OK, you probably could remove real paint from real glass, either chemically or by scraping. No analogy is perfect. ☺
Retain aPath after you create it, since it will have been autoreleased by the time the mouseDown: message is sent. Just make sure to release it in your class's dealloc or wherever else that it would be appropriate.
Edit:
Refer to Matt Ball's answer instead.