web-dev-qa-db-ja.com

丸い角と影のある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:

NSShadowの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;

    NSShadow *dropShadow = [[NSShadow alloc] init];
    [dropShadow setShadowColor:[NSColor blackColor]];
    [dropShadow setShadowBlurRadius:10.0];
    [aView setShadow: dropShadow];

    [super setContentView:aView];
}

@end
21
dhrm

更新

古いアプローチでは正確なラウンドコーナーを作成できないことに気づきました。そこで、例を更新して正確な丸い角を作りました。

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)

ここ の完全な実例。


Oudated

少なくともOSX 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()
}

ここ から実用的な例をダウンロードできます。

15
Eonil

Apple Developer Site にサンプルアプリケーションがあり、それが役立つかもしれません。このサンプルは、カスタムシェイプ、タイトルバーなし、透明なコンテンツを使用してウィンドウを作成する方法を示しています。また、ウィンドウの形状を変更し、ウィンドウの境界の周りのドロップシャドウを再計算する方法も示します。

3

アプリケーション用に独自のバージョンのXcodeスプラッシュ画面を作成しました。これには、最近のファイルリストビューが含まれ、四隅がすべて丸みを帯びています。

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

これを行うために私が見つけた最も簡単な方法は、InterfaceBuilderを使用してウィンドウを作成することでした。ウィンドウを800x470ピクセルにした後、「シャドウ」と「復元可能」を除くすべてのオプションをチェックを外しました。これにより、スプラッシュ画面を作成するための白紙の状態が残りました。

スプラッシュウィンドウのinitWithContentRect:styleMask:backing:defer:メソッドで、次のプロパティも設定します。

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

この時点でウィンドウを表示した場合、背景はなく、ウィンドウの影はウィンドウ内の不透明なコントロールの背後に自動的に設定されます。

ウィンドウの背景を描くときは、左側の500ピクセルを薄い灰色で塗りつぶし、右側の残りの300ピクセルを白で塗りつぶします。表示された場合、この時点でウィンドウに丸い角はありません。

次に、[NSColor clearColor]を使用して、ウィンドウの4つの角すべてを切り欠きます(正方形のサイズは、次に描画する丸い角の半径です)。次に、ベジェパスを使用して、切り取ったノッチに丸い角を描きます。

[NSColor clearColor]で埋めると角が丸くなるベジェパスを作成してこれを実行しようとしましたが、他の色で塗りつぶされても、何らかの理由でパスがクリアで塗りつぶされませんでした。

これで、ウィンドウをレンダリングすると角が丸くなりますが、テーブルビューをウィンドウの右側にドロップすると、角は再び正方形になります。簡単な解決策は、背景を描画しないようにNSScrollViewを設定してから、ネストされたNSTableView背景を透明に設定することです。

テーブルの後ろに背景を描画しているので、背景を描画するテーブルやスクロールビューは実際には必要ありません。これにより、丸みを帯びた角が保持されます。

四隅の近くにある他のコントロールがある場合は、丸みを帯びた領域に入らないように十分にインデントしてください。この例として、ウィンドウの左下にボタンがありますが、ボタンには透明度があるため、丸い角は削除されません。

もう1つの考慮事項は、ウィンドウサブクラスにcanBecomeKeyWindowメソッドとcanBecomeMainWindowメソッドを指定して、これらのタイプのウィンドウのデフォルトがYESであるためNOを返すことです。 。

この情報がウィンドウの作成に役立つことを願っています。しばらく前にあなたの質問を見て、戻ってきて、ウィンドウの作成方法をお知らせしたいと思いました。 :)

3
Paige DePol

@ Eonilanswer のObjective 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];
3
Daniel Storm

Apple Developer Documentationが完全に指摘しているように:

レイヤーにシャドウを追加する場合、シャドウはレイヤーのコンテンツの一部ですが、実際にはレイヤーの境界の長方形の外側に広がります。その結果、レイヤーのmasksToBoundsプロパティを有効にすると、シャドウ効果がエッジの周りでクリップされます。レイヤーに透明なコンテンツが含まれている場合、レイヤーの真下の影の部分は表示されますが、レイヤーを超えて伸びる部分は表示されないという奇妙な効果が発生する可能性があります。シャドウが必要であると同時に境界マスキングも使用する場合は、1つではなく2つのレイヤーを使用します。コンテンツを含むレイヤーにマスクを適用してから、シャドウ効果が有効になっているまったく同じサイズの2番目のレイヤー内にそのレイヤーを埋め込みます。

記事全文: https://developer.Apple.com/library/mac/documentation/cocoa/conceptual/coreanimation_guide/SettingUpLayerObjects/SettingUpLayerObjects.html#//Apple_ref/doc/uid/TP40004514-CH13-SW12 ==

したがって、実際には、2つのビュー/レイヤーを操作する必要があります。

- (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
Axel Purgauer

あなたは私が持っていたのと同じ問題に苦しんでいます、しかし良いニュースは私がそれを機能させるためにかなり簡単な方法を見つけたということです。

したがって、概念的には、Xcodeのウェルカム画面を見ると、通常のウィンドウのように見えますが、タイトルバーはありません(ありますか?) 。

ウィンドウ設定

私がやったことは、通常のウィンドウを取り、それを選択した状態で、Atribute Inspectorに移動し、タイトルバーの3つのボタンである[閉じる]、[最小化]、[サイズ変更]ボタンを無効にしました。 。

したがって、「裸の」ウィンドウが表示されますが、タイトルバーは表示されます。できることは、次のコードを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のウェルカム画面にかなり近く見えるはずです。必要に応じて、ホバー効果をプログラムすることもできます。これは非常に簡単です。

1
Rafael Borges

2つのオプションがあります。

  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
1
Mike Lischke

以前のプロジェクトでこれに遭遇しました。レイヤーに裏打ちされたビューは影を生成しません。ウィンドウのコンテンツビューを「通常の」(レイヤーに裏打ちされていない)ビューに設定すると、シャドウが表示されます。

0
Gerd K

これを試して:

- (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];
}
0
the Reverend

ビューの-drawRect:で、描画後、[[self window] invalidateShadow];を呼び出します。

0
Ken Thomases

InterfaceBuilderと2行の追加コードだけで実行できるソリューションは次のとおりです。

ウィンドウを含むNIBファイルをInterfaceBuilderで作成します。
次のように構成します。

NSWindow without any decorations

Box(NSBox)オブジェクトをInterface Builderのウィンドウコンテンツビューに追加し、コンテンツビュー全体に表示します。必要に応じて、ボックスの境界線のスタイル、境界線の太さ、境界線の色、塗りつぶしの色、およびコーナーの半径を設定します。

NSBox with default values

他のすべてのUIコンテンツをボックスに追加します。これは、ウィンドウコンテンツビューであるかのように扱います。

コードで、このNIBファイルをロードして所有者になるNSWindowControllerのサブクラスを作成します。 -awakeFromNibまたは-windowDidLoadのいずれかに、次のコードを追加します。

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

それはすべての人々です。
レイヤー、カスタム描画コード、NSBezierPathNSShadowはありません。
macOS10.9から10.15までのすべてのシステムで正しく動作することを確認しました

0
Mecki