How to add an NSView to NSWindow in a Cocoa app?

27,358

Solution 1

Use setWantsLayer: method of NSView class.

NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(100, 100, 100, 100)];
[view setWantsLayer:YES];
view.layer.backgroundColor = [[NSColor yellowColor] CGColor];

[self.window.contentView addSubview:view];

NSRect frame = NSMakeRect(10, 40, 90, 40);
NSButton* pushButton = [[NSButton alloc] initWithFrame: frame]; 
pushButton.bezelStyle = NSRoundedBezelStyle;

[self.window.contentView addSubview:pushButton];

NSLog(@"subviews are %@", [self.window.contentView subviews]);  

Solution 2

To expand on the suggestion by Kevin Ballard, the classic way to do this is to subclass NSView and override the -drawRect: method. NSRectFill is a very convenient function for filling a rectangle without having to create a bezier path:

- (void)drawRect:(NSRect)rect
{
    [[NSColor yellowColor] set];
    NSRectFill(rect);
}

Solution 3

NSViews in Cocoa are, by default, not layer-backed. I suspect that if you type

NSLog(@"%@", view.layer);

you will see that it is nil.

In iOS, all views have layers. But on OS X, views don't have layers. In addition, there's 2 "modes" of layer-backed views on OS X. There's what's called a "layer-backed views" and a "layer-hosting view". A layer-backed view uses a CoreAnimation layer to cache drawn data, but you are not allowed to interact with the layer in any way. A layer-hosting view uses a CALayer that you explicitly provide, and you may mess with that layer all you want. However, with a layer-hosting view you may not add any subviews, or use the built-in NSView drawing mechanism. A layer-hosting view must only be used as the root of a CoreAnimation layer hierarchy.

Given all this, you should probably avoid using CoreAnimation at all for your view.

It's possible that an NSBox will do what you want. You can certainly set a fill color there, turn off the border, and set the style to custom. I'm just not 100% certain it will draw as a simple filled rectangle of color. Alternatively you can define your own NSView subclass that draws a color in -drawRect:.

Share:
27,358
Jeremy L
Author by

Jeremy L

Updated on July 05, 2022

Comments

  • Jeremy L
    Jeremy L almost 2 years

    Since the template of an OS X app in Xcode seems to be similar to an empty app template, the following is used to add a view and a button (trying not to use Interface builder for now):

    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
    {       
        NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(100, 100, 100, 100)];
    
        view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
    
        [self.window.contentView addSubview:view];
    
        NSRect frame = NSMakeRect(10, 40, 90, 40);
        NSButton* pushButton = [[NSButton alloc] initWithFrame: frame]; 
        pushButton.bezelStyle = NSRoundedBezelStyle;
    
        [self.window.contentView addSubview:pushButton];
    
        NSLog(@"subviews are %@", [self.window.contentView subviews]);   
    }
    

    Similar code on iOS should have produced a yellow box and a button, but the code above only produce a button, but the view won't show. Is there something wrong with the code above, and how to make it show the view with a yellow background?

  • Jeremy L
    Jeremy L over 11 years
    do you know how to make the view show with a yellow background?
  • Lily Ballard
    Lily Ballard over 11 years
    @JeremyL: I'm researching that right now. I primarily do iOS, so please hold on.
  • Jeremy L
    Jeremy L over 11 years
    NSView doesn't have a backgroundColor property, it seems. I kind of miss UIKit...!
  • rdelmar
    rdelmar over 11 years
    @JeremyL, I always do as Kevin mentioned, use an NSBox instead of a custom view. Just set its type to custom, and then you can (in IB or code) set its background color, a border color (or no border if you choose), and round corners if you wish.
  • Lily Ballard
    Lily Ballard over 11 years
    This answer is bad. You are explicitly told not to touch the backing layer in a layer-backed view (a view where you used -setWantsLayer: without calling -setLayer: first). From the docs: When using layer-backed views you should never interact directly with the layer
  • Jeremy L
    Jeremy L over 11 years
    you mean, should first instantiate a CALayer, set it to view.layer, and then set wantsLayer to YES, and set background color?
  • Jeremy L
    Jeremy L over 11 years
    I tried an NSBox, but it has a "title" with it, and there is no backgroundColor property
  • Lily Ballard
    Lily Ballard over 11 years
    @JeremyL: You can nil out the title, set the box type to custom, set the border type to none. The background color is then controlled by the fillColor. I would encourage you to create a xib, drag an NSBox onto your canvas, and play with the attributes there.
  • Lily Ballard
    Lily Ballard over 11 years
    @JeremyL: No, because a layer-hosting view (which is what you get if you set the layer) cannot have any subviews. If you want your button to be on top of the background, it needs to be a subview (overlapping views that aren't hierarchical may not work properly, and almost certainly won't if one is layer-hosting). Since your button would need to be a subview, the yellow background cannot be a layer-hosting view.
  • rdelmar
    rdelmar over 11 years
    @JeremyL, you tried a box, but didn't set its type to custom -- the custom box has no title and does have fill color, border color and border type.
  • Hope
    Hope about 8 years
    How do you link a custom class to that view ? @NSAddict