I\'m trying to crate a NSWindow
without title bar (NSBorderlessWindowMask
) with round corners and a shadow, similar to the below \"Welcome
Here's a solution that can be done just with Interface Builder and two lines of extra code:
Create a NIB file in Interface Builder containing your window.
Configure it as shown below:
Add a box (NSBox
) object to the window content view in Interface Builder and make it fill the entire content view. Set border style, border thickness, border color, fill color and corner radius of the box as desired:
Add all your other UI content to the box, treat is as if it was your window content view.
In your code, create a subclass of NSWindowController
which loads this NIB file and thus becomes the owner. Either in -awakeFromNib
or -windowDidLoad
, add the following code:
self.window.opaque = NO;
self.window.backgroundColor = NSColor.clearColor;
That's all folks.
No layers, no custom drawing code, no NSBezierPath
, no NSShadow
.
Verified to work correctly on all systems from macOS 10.9 to 10.15
As the Apple Developer Documentation perfectly points out:
When adding shadows to a layer, the shadow is part of the layer’s content but actually extends outside the layer’s bounds rectangle. As a result, if you enable the masksToBounds property for the layer, the shadow effect is clipped around the edges. If your layer contains any transparent content, this can cause an odd effect where the portion of the shadow directly under your layer is still visible but the part extending beyond your layer is not. If you want a shadow but also want to use bounds masking, you use two layers instead of one. Apply the mask to the layer containing your content and then embed that layer inside a second layer of the exact same size that has the shadow effect enabled.
Full Article: https://developer.apple.com/library/mac/documentation/cocoa/conceptual/coreanimation_guide/SettingUpLayerObjects/SettingUpLayerObjects.html#//apple_ref/doc/uid/TP40004514-CH13-SW12
So in fact you have to work with two views/layers:
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
{
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag];
if ( self )
{
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
[self setMovableByWindowBackground:TRUE];
[self setStyleMask:NSBorderlessWindowMask];
[self setHasShadow:YES];
}
return self;
}
- (BOOL)canBecomeKeyWindow {
return YES;
}
-(BOOL)canBecomeMainWindow {
return YES;
}
- (void) setContentView:(NSView *)aView {
NSView *backView = [[NSView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
backView.wantsLayer = YES;
backView.layer.masksToBounds = NO;
backView.layer.shadowColor = [NSColor shadowColor].CGColor;
backView.layer.shadowOpacity = 0.5;
backView.layer.shadowOffset = CGSizeMake(0, -3);
backView.layer.shadowRadius = 5.0;
backView.layer.shouldRasterize = YES;
NSView *frontView = [aView initWithFrame:CGRectMake(backView.frame.origin.x + 15, backView.frame.origin.y + 15, backView.frame.size.width - 30, backView.frame.size.height - 30)];
[backView addSubview: frontView];
frontView.layer.cornerRadius = 8;
frontView.layer.masksToBounds = YES;
frontView.layer.borderColor = [[NSColor darkGrayColor] CGColor];
frontView.layer.borderWidth = 0.5;
[super setContentView:backView];
}
Have a closer look at the "initWithFrame" part of the code. Best greetings from Tyrol, Austria!
try this:
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)deferCreation
{
self = [super
initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask | NSResizableWindowMask
backing:bufferingType
defer:deferCreation];
if (self)
{
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
}
[self setHasShadow:YES];
[self setMovableByWindowBackground:YES];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(didBecomeKey:) name:NSWindowDidBecomeKeyNotification object:self];
return self;
}
-(void)didBecomeKey:(NSNotification *)notify{
[self performSelector:@selector(invalidateShadow) withObject:nil afterDelay:.1];
}
I ran across this in a previous project: layer-backed views do not produce a shadow. Set your window's content view to a "regular" (non-layer-backed) view and it will have a shadow.
In your view's -drawRect:
, after the drawing, invoke [[self window] invalidateShadow];
.
You are struggling with the same issue I had, but the good news is that I have found a way pretty much easy to make it work.
So, to the concepts, if you look at Xcode's welcome screen, it pretty much looks like a regular window but has no title bar (does it?).
What I've done is I've taken a regular window and with it selected, I've gone to the Atribute Inspector and deactivated the "Close", "Minimize" and "Resize" buttons which are the three buttons at the title bar.
So you get a "naked" window, but still has the title bar. What you can do is to add the following code to your awakeFromNib delegate:
[self.window setTitle:@""];
Assuming your window is declared like in the header file:
@property (assign) IBOutlet NSWindow *window;
Now you have a completely naked title bar and what you can do is some final tweaking:
You can do this in the awakeFromNib delegate as well:
[self.window setBackgroundColor: [NSColor colorWithCalibratedWhite:0.97 alpha:1.0]];
I've used the following method: Adding a button or view to the NSWindow title bar
So I can just use (I've done that also in the awakeFromNib delegate):
//Creating the button:
NSButton *closeButton = [[NSButton alloc] initWithFrame:NSMakeRect(0,0,12,12)];
NSButtonCell *closeButtonCell = [closeButton cell];
[closeButton setBezelStyle:NSCircularBezelStyle];
[closeButton setTitle:@""];
[closeButton setBordered:NO];
[closeButton setImage:[NSImage imageNamed:NSImageNameStopProgressFreestandingTemplate]];
[closeButtonCell setImageScaling:NSImageScaleProportionallyDown];
[closeButtonCell setBackgroundColor:[NSColor clearColor]];
[closeButton setAlphaValue:0.5];
//Calling the button:
[self.window addViewToTitleBar:closeButton atXPosition:8];
Now it should look pretty much close to the xcode's welcome screen, now if you want, you can also program the hover effects, which should be pretty easy.