NSWindow有圆角和阴影

时间:2013-11-12 21:13:54

标签: objective-c cocoa nsview nswindow

我正在尝试使用圆角和阴影来设置NSWindow没有标题栏NSBorderlessWindowMask),类似于下面的“欢迎使用Xcode”窗口。

Welcome to Xcode

我创建了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

NSView的子类:

@implementation ColoredView

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];

    [[NSColor windowBackgroundColor] set];
    NSRectFill(dirtyRect);
}

@end

这给了我一个没有带圆角的标题栏的窗口,但NSWindow上的默认阴影消失了。如何将默认阴影添加到此窗口?

Flat window

EDIT1:

NSWindow with NSShadow。这个阴影没有显示。

@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

11 个答案:

答案 0 :(得分:13)

更新

我意识到旧方法无法创建精确的圆角。所以我更新了例子来制作精确的圆角。

enter image description here

        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完整的工作示例。


Oudated

至少在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()
}

您可以从here下载一个工作示例。

答案 1 :(得分:3)

Apple开发人员文档完美指出:

  

向图层添加阴影时,阴影是图层的一部分   内容但实际上扩展到图层的边界矩形之外。如   结果,如果为图层启用了masksToBounds属性,则   阴影效果被修剪边缘。如果您的图层包含任何   透明的内容,这可能会导致部分的奇怪效果   您图层正下方的阴影仍然可见,但部分   延伸到你的图层之外的不是。如果你想要一个阴影但也想要   要使用边界遮罩,可以使用两个图层而不是一个图层。适用   屏蔽包含内容的图层,然后嵌入该图层   在具有阴影的完全相同大小的第二层内   效果已启用。

完整文章:https://developer.apple.com/library/mac/documentation/cocoa/conceptual/coreanimation_guide/SettingUpLayerObjects/SettingUpLayerObjects.html#//apple_ref/doc/uid/TP40004514-CH13-SW12

所以实际上你必须使用两个视图/层:

- (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];

}

仔细查看代码的“initWithFrame”部分。 来自奥地利蒂罗尔的最好的问候!

答案 2 :(得分:3)

我刚为我的应用程序创建了自己的Xcode启动画面版本,包括最近的文件列表视图,所有四个角都是四舍五入的:

http://www.fizzypopstudios.com/splash.png

我发现这样做的最简单方法是使用界面构建器创建一个窗口。我将窗口设置为800x470像素,然后我取消选中除“阴影”和“可恢复”之外的所有选项。这给我留下了一块空白的石板,用它来创建我的闪屏。

在启动窗口的initWithContentRect:styleMask:backing:defer:方法中,我还设置了以下属性:

self.opaque = NO
self.backgroundColor = [NSColor clearColor]
self.movableByWindowBackground = YES

如果此时显示窗口,则没有背景,窗口阴影将自动设置为窗口中任何非透明控件的后面。

当我绘制窗口背景时,我用左边的500像素填充浅灰色,右边的剩余300像素填充白色。如果显示,此时窗口将没有圆角。

然后我使用[NSColor clearColor]来划分窗口的所有4个角(正方形的大小是我们接下来绘制的圆角的半径)。然后使用Bezier路径,我在一个圆角处画入我刚切出的凹槽。

我尝试通过创建一个bezier路径来实现这一点,当填充[NSColor clearColor]时也会产生圆角,但是,由于某种原因,路径不会填充清晰,尽管它会填充其他颜色

现在,如果渲染窗口,则会有圆角,但是,如果将桌面视图放入窗口的右侧,角落将再次变为方形。简单的解决方案是将NSScrollView设置为不绘制背景,然后将嵌套的NSTableView背景设置为透明。

当我们绘制表格背后的背景时,我们并不需要绘制背景的表格或滚动视图,这样就可以保留圆角。

如果你有任何其他控件靠近4个角落,只需保持它们足够的缩进,不要在圆角区域内。这方面的一个例子是我的窗口左下角有一个按钮,但由于按钮具有透明度,因此不会删除圆角。

另一个考虑因素是您可能希望在窗口子类中提供canBecomeKeyWindowcanBecomeMainWindow方法以返回YES,因为这些类型的窗口的默认值为NO

我希望这些信息可以帮助您创建窗口,我前一段时间看到了您的问题,并认为我会回来让您知道我是如何创建窗口的,以防它可以帮到您! :)

答案 3 :(得分:2)

Apple Developer Site上有一个示例应用程序可以帮助您。此示例演示如何使用自定义形状,无标题栏和透明内容创建窗口。它还显示了如何更改窗口的形状并重新计算窗口边框周围的阴影。

答案 4 :(得分:2)

@Eonilanswer的目标C示例:

[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];

答案 5 :(得分:1)

您有两种选择:

  1. 使用layer's shadow properties
  2. 在drawRect例程中自己绘制阴影。为此,请不要设置圆角,而是使用插入路径绘制圆角矩形及其阴影。
  3. 代码:

    @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
    

答案 6 :(得分:1)

你正在努力解决我遇到的同样问题,但好消息是我找到了一种非常容易实现的方法。

所以,对于这些概念,如果你看看Xcode的欢迎屏幕,它几乎看起来像一个常规窗口,但没有标题栏(是吗?)。

窗口设置

我所做的是我已经选择了常规窗口并选择了它,我已经去了Atribute Inspector并停用了“关闭”,“最小化”和“调整大小”按钮,这三个按钮位于标题栏。

所以你得到一个“裸体”窗口,但仍然有标题栏。您可以做的是将以下代码添加到您的awakeFromNib委托:

[self.window setTitle:@""];

假设您的窗口在头文件中声明:

@property (assign) IBOutlet NSWindow *window;

现在你有一个完全裸的标题栏,你可以做的是最后的调整:

颜色

您也可以在awakeFromNib委托中执行此操作:

[self.window setBackgroundColor: [NSColor colorWithCalibratedWhite:0.97 alpha:1.0]];

关闭按钮

我使用了以下方法: Adding a button or view to the NSWindow title bar

所以我可以使用(我也在awakeFromNib委托中也这样做了):

//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];

现在它应该看起来非常接近xcode的欢迎屏幕,现在如果你愿意,你也可以设置悬停效果,这应该很容易。

答案 7 :(得分:1)

这是一个只需使用Interface Builder和两行额外的代码即可完成的解决方案:

在包含窗口的Interface Builder中创建NIB文件。
如下所示进行配置:

NSWindow without any decorations

在Interface Builder中向窗口内容视图添加一个框(NSBox)对象,并使其填充整个内容视图。根据需要设置框的边框样式,边框厚度,边框颜色,填充颜色和拐角半径:

NSBox with default values

将所有其他UI内容添加到框中,就像对待窗口内容视图一样。

在您的代码中,创建NSWindowController的子类,该子类加载此NIB文件并因此成为所有者。在-awakeFromNib-windowDidLoad中,添加以下代码:

    self.window.opaque = NO;
    self.window.backgroundColor = NSColor.clearColor;

这就是所有人。
没有图层,没有自定义工程图代码,没有NSBezierPath,没有NSShadow
经过验证可以在macOS 10.9到10.15的所有系统上正常工作

答案 8 :(得分:0)

我在之前的项目中遇到过这个问题:图层支持的视图不会产生阴影。将窗口的内容视图设置为“常规”(非图层支持)视图,它将具有阴影。

答案 9 :(得分:0)

试试这个:

- (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 :(得分:0)

在您的视图-drawRect:中,在绘图后,调用[[self window] invalidateShadow];