问题
I'm trying to crate a NSWindow
without title bar (NSBorderlessWindowMask
) with round corners and a shadow, similar to the below "Welcome to Xcode" window.
I make a subclass of NSWindow
:
@implementation FlatWindow
- (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;
}
- (void) setContentView:(NSView *)aView
{
aView.wantsLayer = YES;
aView.layer.frame = aView.frame;
aView.layer.cornerRadius = 10.0;
aView.layer.masksToBounds = YES;
[super setContentView:aView];
}
@end
And a subclass of NSView
:
@implementation ColoredView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[[NSColor windowBackgroundColor] set];
NSRectFill(dirtyRect);
}
@end
This gives me a window without title bar with round corners, but the default shadow on NSWindow
is gone. How can I add the default shadow to this window?
EDIT1:
NSWindow with NSShadow
. This shadow is not shown.
@implementation FlatWindow
- (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;
}
- (void) setContentView:(NSView *)aView
{
aView.wantsLayer = YES;
aView.layer.frame = aView.frame;
aView.layer.cornerRadius = 10.0;
aView.layer.masksToBounds = YES;
NSShadow *dropShadow = [[NSShadow alloc] init];
[dropShadow setShadowColor:[NSColor blackColor]];
[dropShadow setShadowBlurRadius:10.0];
[aView setShadow: dropShadow];
[super setContentView:aView];
}
@end
回答1:
Update
I realised old approach was not able to create precise round corner. So I updated example to make precise round corner.
window1.backgroundColor = NSColor.whiteColor()
window1.opaque = false
window1.styleMask = NSResizableWindowMask
| NSTitledWindowMask
| NSFullSizeContentViewWindowMask
window1.movableByWindowBackground = true
window1.titlebarAppearsTransparent = true
window1.titleVisibility = .Hidden
window1.showsToolbarButton = false
window1.standardWindowButton(NSWindowButton.FullScreenButton)?.hidden = true
window1.standardWindowButton(NSWindowButton.MiniaturizeButton)?.hidden = true
window1.standardWindowButton(NSWindowButton.CloseButton)?.hidden = true
window1.standardWindowButton(NSWindowButton.ZoomButton)?.hidden = true
window1.setFrame(CGRect(x: 400, y: 0, width: 400, height: 500), display: true)
window1.makeKeyAndOrderFront(self)
Here's full working example.
Oudated
Special treatment is not required at least in OS X 10.10.
import Cocoa
class ExampleApplicationController: NSObject, NSApplicationDelegate {
class ExampleController {
let window1 = NSWindow()
let view1 = NSView()
init(){
window1.setFrame(CGRect(x: 400, y: 0, width: 400, height: 500), display: true)
window1.contentView = view1
window1.backgroundColor = NSColor.clearColor()
window1.opaque = false
window1.styleMask = NSBorderlessWindowMask | NSResizableWindowMask
window1.movableByWindowBackground = true
window1.makeKeyAndOrderFront(self)
view1.wantsLayer = true
view1.layer!.cornerRadius = 10
view1.layer!.backgroundColor = NSColor.whiteColor().CGColor
/// :ref: http://stackoverflow.com/questions/19940019/nswindow-with-round-corners-and-shadow/27613308#21247949
window1.invalidateShadow() // This manual invalidation is REQUIRED because shadow generation is an expensive operation.
}
}
let example1 = ExampleController()
}
You can download a working example from here.
回答2:
I just created my own version of the Xcode splash screen for my application, including the recent files list view and all four corners are rounded:
http://www.fizzypopstudios.com/splash.png
The easiest way I found to do this was to create a window using the interface builder. I made the window 800x470 pixles, then I unchecked all the options except "Shadow" and "Restorable". This left me with a blank slate with which to create my splash screen.
In the initWithContentRect:styleMask:backing:defer:
method for the splash window I also set the following properties:
self.opaque = NO
self.backgroundColor = [NSColor clearColor]
self.movableByWindowBackground = YES
If you displayed the window at this point you would have no background, and the window shadow would automatically be set to be behind any non-transparent controls in the window.
When I draw my window background I fill the left 500 pixels with light gray and the remaining 300 pixels on the right with white. If displayed the window would have no rounded corners at this point.
I then use [NSColor clearColor]
to notch squares out all 4 corners of the window (the size of the square is the radius of the rounded corner we will draw next). Then using Bezier Paths I draw in a rounded corner into the notches I just cut out.
I tried to do this by creating a bezier path that when filled with [NSColor clearColor]
would also result in a round corner, however, for some reason the path would not fill with clear, though it would fill with other colors.
Now, if you render the window you will have rounded corners, however, if you drop a table view into the right side of the window the corners will become square again. The easy solution there is to set the NSScrollView
to not draw a background, and then to set the nested NSTableView
background to transparent.
As we are drawing the background behind the table we don't really need the table or scroll view drawing a background, and this then preserves the rounded corners.
If you have any other controls that go near the 4 corners just keep them indented enough to not be in the rounded area. An example of this is I have a button in the lower left of my window, however, since the button has transparency the rounded corner is not removed.
One other consideration is you may want to provide canBecomeKeyWindow
and canBecomeMainWindow
methods in your window subclass to return YES
as the default for these types of windows is NO
.
I hope this info helps you create your window, I saw your question a while ago and thought I would come back and let you know how I created my window in case it could help you! :)
回答3:
Objective C example of @Eonil's answer:
[window setBackgroundColor:[NSColor whiteColor]];
[window setOpaque:NO];
[window setStyleMask:NSResizableWindowMask | NSTitledWindowMask | NSFullSizeContentViewWindowMask];
[window setMovableByWindowBackground:YES];
[window setTitlebarAppearsTransparent:YES];
[window setTitleVisibility:NSWindowTitleHidden];
[window setShowsToolbarButton:NO];
[window standardWindowButton:NSWindowFullScreenButton].hidden = YES;
[window standardWindowButton:NSWindowMiniaturizeButton].hidden = YES;
[window standardWindowButton:NSWindowCloseButton].hidden = YES;
[window standardWindowButton:NSWindowZoomButton].hidden = YES;
[window makeKeyWindow];
回答4:
There is a sample application on the Apple Developer Site that may help you with that. This sample demonstrates how to create windows with custom shapes, no title bar, and transparent content. It also shows how to change the shape of the window and recalculate the drop shadow around the window border.
回答5:
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!
回答6:
You have two options:
- Use the layer's shadow properties.
- Draw the shadow yourself in your drawRect routine. For this to work don't set the rounded corners but use an inset path to draw the rounded rectangle and its shadow.
Code:
@interface RoundedOuterShadowView : NSView {
}
@end
@implementation RoundedOuterShadowView
- (id)initWithFrame: (NSRect)frameRect
{
self = [super initWithFrame: frameRect];
if (self != nil) {
}
return self;
}
// Shared objects.
static NSShadow *borderShadow = nil;
- (void)drawRect: (NSRect)rect
{
[NSGraphicsContext saveGraphicsState];
// Initialize shared objects.
if (borderShadow == nil) {
borderShadow = [[NSShadow alloc] initWithColor: [NSColor colorWithDeviceWhite: 0 alpha: 0.5]
offset: NSMakeSize(1, -1)
blurRadius: 5.0];
}
// Outer bounds with shadow.
NSRect bounds = [self bounds];
bounds.size.width -= 20;
bounds.size.height -= 20;
bounds.origin.x += 10;
bounds.origin.y += 10;
NSBezierPath *borderPath = [NSBezierPath bezierPathWithRoundedRect: bounds xRadius: 5 yRadius: 5];
[borderShadow set];
[[NSColor whiteColor] set];
[borderPath fill];
[NSGraphicsContext restoreGraphicsState];
}
@end
回答7:
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?).
Window Setup
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:
Color
You can do this in the awakeFromNib delegate as well:
[self.window setBackgroundColor: [NSColor colorWithCalibratedWhite:0.97 alpha:1.0]];
Close Button
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.
回答8:
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.
回答9:
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];
}
回答10:
In your view's -drawRect:
, after the drawing, invoke [[self window] invalidateShadow];
.
来源:https://stackoverflow.com/questions/19940019/nswindow-with-round-corners-and-shadow