30

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

欢迎来到 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消失了。如何将默认阴影添加到此窗口?

平窗

编辑1:

NSWindow 与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
4

11 回答 11

21

更新

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

在此处输入图像描述

        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)

是完整的工作示例。


旧的

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

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

于 2014-12-23T02:35:23.317 回答
4

@Eonil答案的目标 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];
于 2016-05-24T16:18:11.167 回答
3

我刚刚为我的应用程序创建了我自己版本的 Xcode 初始屏幕,包括最近的文件列表视图,并且所有四个角都是圆角的:

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

我发现最简单的方法是使用界面生成器创建一个窗口。我将窗口设为 800x470 像素,然后取消选中除“Shadow”和“Restorable”之外的所有选项。这给我留下了一张空白的石板,可以用来创建我的启动画面。

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

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

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

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

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

我试图通过创建一个贝塞尔路径来做到这一点,当填充时[NSColor clearColor]也会导致圆角,但是,由于某种原因,路径不会填充清晰,尽管它会填充其他颜色。

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

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

如果您有任何其他控件靠近 4 个角,只需将它们缩进到足以不在圆形区域中即可。这方面的一个例子是我的窗口左下角有一个按钮,但是,由于该按钮具有透明度,因此不会删除圆角。

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

我希望这些信息可以帮助您创建窗口,我不久前看到了您的问题,并认为我会回来告诉您我是如何创建窗口的,以防它可以帮助您!:)

于 2014-01-26T00:45:17.053 回答
3

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

于 2013-11-13T09:11:06.967 回答
2

正如 Apple Developer Documentation 完美指出的那样:

向图层添加阴影时,阴影是图层内容的一部分,但实际上延伸到图层的边界矩形之外。因此,如果您启用该层的 maskToBounds 属性,阴影效果将被剪裁在边缘周围。如果您的图层包含任何透明内容,这可能会导致奇怪的效果,即直接在图层下方的阴影部分仍然可见,但超出图层的部分不可见。如果您想要阴影但又想使用边界遮罩,则使用两层而不是一层。将蒙版应用到包含您的内容的图层,然后将该图层嵌入到启用阴影效果的大小完全相同的第二层中。

全文: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”部分。来自奥地利蒂罗尔的问候!

于 2014-01-25T20:52:31.207 回答
2

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

在 Interface Builder 中创建一个包含您的窗口的 NIB 文件。
如下图配置:

没有任何装饰的 NSWindow

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

具有默认值的 NSBox

将所有其他 UI 内容添加到框中,就好像它是您的窗口内容视图一样。

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

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

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

于 2020-02-24T21:10:16.153 回答
1

你有两个选择:

  1. 使用图层的阴影属性
  2. 在您的 drawRect 例程中自己绘制阴影。为此,不要设置圆角,而是使用插入路径来绘制圆角矩形及其阴影。

代码:

@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
于 2013-11-13T09:04:01.490 回答
1

您正在为与我遇到的相同问题而苦苦挣扎,但好消息是我找到了一种非常容易使其工作的方法。

所以,就概念而言,如果您查看 Xcode 的欢迎屏幕,它看起来很像一个常规窗口,但没有标题栏(是吗?)。

窗口设置

我所做的是我采取了一个常规窗口并选择了它,我去了属性检查器并停用了标题栏上的三个按钮“关闭”、“最小化”和“调整大小”按钮.

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

[self.window setTitle:@""];

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

@property (assign) IBOutlet NSWindow *window;

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

颜色

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

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

关闭按钮

我使用了以下方法: 向 NSWindow 标题栏添加按钮或视图

所以我可以使用(我在 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 的欢迎屏幕,现在如果你愿意,你还可以编写悬停效果,这应该很容易。

于 2014-02-28T15:35:28.230 回答
0

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

于 2013-11-12T21:33:50.373 回答
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];
}
于 2014-01-20T23:28:27.023 回答
0

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

于 2014-01-21T02:22:10.547 回答