NSWindow Shadow Outline - objective-c

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.

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.

How to add an NSView to NSWindow in a Cocoa app?

Since the template of an OS X app in Xcode seems to be similar to an empty app template, the following is used to add a view and a button (trying not to use Interface builder for now):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(100, 100, 100, 100)];
view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
[self.window.contentView addSubview:view];
NSRect frame = NSMakeRect(10, 40, 90, 40);
NSButton* pushButton = [[NSButton alloc] initWithFrame: frame];
pushButton.bezelStyle = NSRoundedBezelStyle;
[self.window.contentView addSubview:pushButton];
NSLog(#"subviews are %#", [self.window.contentView subviews]);
}
Similar code on iOS should have produced a yellow box and a button, but the code above only produce a button, but the view won't show. Is there something wrong with the code above, and how to make it show the view with a yellow background?
Use setWantsLayer: method of NSView class.
NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(100, 100, 100, 100)];
[view setWantsLayer:YES];
view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
[self.window.contentView addSubview:view];
NSRect frame = NSMakeRect(10, 40, 90, 40);
NSButton* pushButton = [[NSButton alloc] initWithFrame: frame];
pushButton.bezelStyle = NSRoundedBezelStyle;
[self.window.contentView addSubview:pushButton];
NSLog(#"subviews are %#", [self.window.contentView subviews]);
To expand on the suggestion by Kevin Ballard, the classic way to do this is to subclass NSView and override the -drawRect: method. NSRectFill is a very convenient function for filling a rectangle without having to create a bezier path:
- (void)drawRect:(NSRect)rect
{
[[NSColor yellowColor] set];
NSRectFill(rect);
}
NSViews in Cocoa are, by default, not layer-backed. I suspect that if you type
NSLog(#"%#", view.layer);
you will see that it is nil.
In iOS, all views have layers. But on OS X, views don't have layers. In addition, there's 2 "modes" of layer-backed views on OS X. There's what's called a "layer-backed views" and a "layer-hosting view". A layer-backed view uses a CoreAnimation layer to cache drawn data, but you are not allowed to interact with the layer in any way. A layer-hosting view uses a CALayer that you explicitly provide, and you may mess with that layer all you want. However, with a layer-hosting view you may not add any subviews, or use the built-in NSView drawing mechanism. A layer-hosting view must only be used as the root of a CoreAnimation layer hierarchy.
Given all this, you should probably avoid using CoreAnimation at all for your view.
It's possible that an NSBox will do what you want. You can certainly set a fill color there, turn off the border, and set the style to custom. I'm just not 100% certain it will draw as a simple filled rectangle of color. Alternatively you can define your own NSView subclass that draws a color in -drawRect:.

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.

Strange drawing behavior of NSSegmentedControl, when superview is drawn

I've got a problem with the NSSegmentedControl.
I have a custom view where I draw a gradient with a shadow.
The drawing is ok, but when I place a NSSegmentedControl inside it then something strange happens - the background of the custom view is shining through the segmented control, as if the segmented control had a low alpha value.
Here are pictures, that demonstrate the issue :
With the custom view in background :
Without the custom view in background :
As you can see in the left upper corner in the first image the segmented control isn't drawing properly.
Here is the drawRect method from the custom view :
- (void)drawRect:(NSRect)dirtyRect {
NSShadow *shadow = [[NSShadow alloc] init];
[shadow setShadowOffset:NSMakeSize(0.0, -6.0)];
[shadow setShadowBlurRadius:3.0];
[shadow setShadowColor:[[NSColor blackColor] colorWithAlphaComponent:0.3]];
[shadow set];
NSGradient *gradient = [[NSGradient alloc] initWithColorsAndLocations:[NSColor colorWithDeviceWhite:0.8 alpha:1.0],(CGFloat)0.0,[NSColor colorWithDeviceWhite:0.65 alpha:1.0],(CGFloat)0.5,[NSColor colorWithDeviceWhite:0.3 alpha:1.0],(CGFloat)0.5,[NSColor colorWithDeviceWhite:0.5 alpha:1.0],(CGFloat)1.0, nil];
[gradient drawInRect:self.bounds angle:90];
NSBezierPath *bezierPath = [NSBezierPath bezierPath];
[bezierPath moveToPoint:NSMakePoint(0, 0)];
[bezierPath lineToPoint:NSMakePoint(-self.bounds.size.height, 0)];
[bezierPath lineToPoint:NSMakePoint(-self.bounds.size.height, -self.bounds.size.width)];
[bezierPath lineToPoint:NSMakePoint(0, -self.bounds.size.width)];
[[NSColor whiteColor] set];
[bezierPath stroke];
}
Can you help me with this?
I don't know how to fix this issue.
I don't know if you're doing any custom drawing in the toolbar buttons, but I think your problem is that you're calling 'set' on the shadow without saving and restoring your graphics context.
Look at the documentation for NSShadow's set method.
NSShadow Documentation
"The shadow attributes of the receiver are used until another shadow is
set or until the graphics state is restored."
You should always save your graphics context first, then call any of Cocoa's set-style methods, then restore the context. This keeps future draw commands from applying your shadow.
NSGraphicsContext *context = [NSGraphicsContext currentContext];
[context saveGraphicsState];
NSShadow *myCoolShadow = [NSShadow new];
// Shadow code here
[myCoolShadow set];
[context restoreGraphicsState];
On 10.7+, Rounded Textured segmented controls and buttons are transparent. On 10.5-10.6, they are gradient filled.

NSTextField like safari address bar

Whats the best way to go about building an address field like the one in safari?
Needs to have editable text, and determinate progress indicator background.
You could just subclass NSTextField and override the -drawRect: method to "fill" the appropriate percentage of the entire width with some color or gradient (or whatever) for the progress. If I'm understanding your question right.
-(void)drawRect:(NSRect)dirtyRect {
CGFloat progress = 0.33;
NSRect progressRect = [self bounds];
progressRect.size.width *= progress;
[[NSColor colorWithCalibratedRed:0.0 green:0.0 blue:1.0 alpha:0.4] set];
NSRectFillUsingOperation(progressRect, NSCompositeSourceOver);
[super drawRect:dirtyRect];
}
Obviously "progress" would come from a property you declare and be updated according to the model. You'd need to make sure setDrawsBackground: is turned off, or the background is set to [NSColor clearColor]; in order for your custom drawing to be seen.
This is a shot from the code above.