162

I'm looking for the best way to change the backgroundColor of an NSView. I'd also like to be able to set the appropriate alpha mask for the NSView. Something like:

myView.backgroundColor = [NSColor colorWithCalibratedRed:0.227f 
                                                   green:0.251f 
                                                    blue:0.337 
                                                   alpha:0.8];

I notice that NSWindow has this method, and I'm not a big fan of the NSColorWheel, or NSImage background options, but if they are the best, willing to use.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
BadPirate
  • 25,802
  • 10
  • 92
  • 123

18 Answers18

143

Yeah, your own answer was right. You could also use Cocoa methods:

- (void)drawRect:(NSRect)dirtyRect {
    // set any NSColor for filling, say white:
    [[NSColor whiteColor] setFill];
    NSRectFill(dirtyRect);
    [super drawRect:dirtyRect];
}

In Swift:

class MyView: NSView {

    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)

        // #1d161d
        NSColor(red: 0x1d/255, green: 0x16/255, blue: 0x1d/255, alpha: 1).setFill()
        dirtyRect.fill()
    }

}
superhawk610
  • 2,457
  • 2
  • 18
  • 27
mohsenr
  • 7,235
  • 3
  • 28
  • 28
  • 18
    `NSRectFill` uses `NSCompositeCopy` to do the fill, so it'll clobber anything behind it—it won't composite on top of any ancestor views. For fills with a partially- (or fully-)transparent color, use `NSRectFillUsingOperation` http://developer.apple.com/mac/library/documentation/Cocoa/Reference/ApplicationKit/Miscellaneous/AppKit_Functions/Reference/reference.html#//apple_ref/c/func/NSRectFillUsingOperation with the `NSCompositeSourceOver` operation. – Peter Hosey Jun 03 '10 at 06:01
  • Oh, that makes sense. I tried NSRectFill but the transparency didn't work. I'm coming to Cocoa from Cocoa Touch, and surprised to see that in some ways the Cocoa Touch framework is more complete (!). I was thinking of making a class extension for NSView that would allow you to set backgroundColor like you can an NSWindow or UIView, but I don't think you can override drawRect with an extension. – BadPirate Jun 03 '10 at 16:31
  • Sorry I missed the transparency part. Yeah, Cocoa touch makes many things easier. If you're also doing Cocoa touch your own answer is actually better, as it's more portable. – mohsenr Jun 03 '10 at 16:55
  • I don't get it, this is drawing the white color on top of the view, I've just tried. Wasn't the question about the background color ? – aneuryzm Feb 14 '13 at 16:05
  • @Patrick - You might need to call super drawRect :) or if you have some other things that are supposed to be drawn on top of the background, make sure that they are after the NSRectFill call. – BadPirate Apr 26 '13 at 19:56
  • I just submitted an edit to fix the code in this answer, but I'd like to mention that `super`'s `drawRect:` method aught to be called before your own. You drawings should appear on top of theirs, not behind theirs. – ArtOfWarfare Nov 18 '13 at 04:12
123

An easy, efficient solution is to configure the view to use a Core Animation layer as its backing store. Then you can use -[CALayer setBackgroundColor:] to set the background color of the layer.

- (void)awakeFromNib {
   self.wantsLayer = YES;  // NSView will create a CALayer automatically
}

- (BOOL)wantsUpdateLayer {
   return YES;  // Tells NSView to call `updateLayer` instead of `drawRect:`
}

- (void)updateLayer {
   self.layer.backgroundColor = [NSColor colorWithCalibratedRed:0.227f 
                                                          green:0.251f 
                                                           blue:0.337 
                                                          alpha:0.8].CGColor;
}

That’s it!

fumoboy007
  • 5,345
  • 4
  • 32
  • 49
loretoparisi
  • 15,724
  • 11
  • 102
  • 146
  • 6
    Careful: calling `setLayer:` or `setWantsLayer:` breaks any WebViews you might have in your application in very creative ways! (Even in different windows...) – rluba Oct 28 '12 at 16:59
  • 1
    In the methode `setWantsLayer:` is defined: _When using layer-backed views you should never interact directly with the layer_. The order of when `setWantsLayer:` and `setLayer:` is called is relevant. – Stephan Sep 14 '13 at 07:59
  • You shouldn't set the layer but can still achieve this. See ColoredView at http://www.objc.io/issues/14-mac/appkit-for-uikit-developers/ – Clafou Jun 17 '15 at 10:05
94

If you are a storyboard lover, here is a way that you don't need any line of code.

Add NSBox as a subview to NSView and adjust NSBox's frame as the same with NSView.

In Storyboard or XIB change Title position to None, Box type to Custom, Border Type to "None", and Border color to whatever you like.

Here is a screenshot:

enter image description here

This is the result:

enter image description here

JZAU
  • 3,550
  • 31
  • 37
  • Using NSBox worked great for me. Switching to a layer-backed view was causing redraw issues, whereas the box did not. – Henrik Oct 30 '16 at 14:16
  • 5
    This is an excellent and simple solution that should be in the docs – UKDataGeek Dec 27 '16 at 00:52
  • Does this color NSPopover triangles though? – Ribena Jun 24 '18 at 05:00
  • i regret only that i have but one upvote to give this answer. – orion elenzil Sep 19 '19 at 16:17
  • I'm trying to use this within an NSTableCellView, but the box's content-view insists on conflicting constraints and kills app on launch. I only made it cover (0 distance lead, trail, top and bottom from superview). for some reason in runtime, one of the "constraints" is that the NSView (content view of NSBox ) has width:0. Kill me if I understand why. – Motti Shneor Aug 10 '21 at 09:47
  • NSBox is a bit of an overkill for simple fill -- as it maintains both a "content view" a text view (its label) a border, and lots of other graphic facilities - you can, of course choose "none" on everything and leave only the fill -- but that's less-than-optimal. – Motti Shneor Aug 18 '21 at 18:58
39

If you setWantsLayer to YES first, you can directly manipulate the layer background.

[self.view setWantsLayer:YES];
[self.view.layer setBackgroundColor:[[NSColor whiteColor] CGColor]];
Gabriel
  • 964
  • 9
  • 8
26

Think I figured out how to do it:

- (void)drawRect:(NSRect)dirtyRect {
    // Fill in background Color
    CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
    CGContextSetRGBFillColor(context, 0.227,0.251,0.337,0.8);
    CGContextFillRect(context, NSRectToCGRect(dirtyRect));
}
BadPirate
  • 25,802
  • 10
  • 92
  • 123
  • Thanks. I needed to convert the dirtyRect to a CGRect. Ammended last line to `CGContextFillRect(context, NSRectToCGRect(dirtyRect));` Can you edit your answer? – Ross Feb 11 '11 at 21:48
  • 1
    They used to be toll free bridged :/ – BadPirate Feb 11 '11 at 22:23
  • 6
    When building for 64-bit or iOS or building 32-bit like 64-bit, NSRect is just another way of saying CGRect. If not, however, these are independent structs, and even though they might consist of the same members, they can never be “toll-free-bridged” as that is a term referring only to objects (structs that have an “isa” pointer and are passed around using pointers), but not to generic structures. – Raphael Schweikert Feb 14 '11 at 11:47
  • using drawRect results in added draw time, etc. CALayer is a better solution in 10.8 and up. – Tom Andersen Nov 27 '15 at 17:50
23

I went through all of these answers and none of them worked for me unfortunately. However, I found this extremely simple way, after about an hour of searching : )

myView.layer.backgroundColor = CGColorCreateGenericRGB(0, 0, 0, 0.9);
David
  • 3,285
  • 1
  • 37
  • 54
greenhouse
  • 1,231
  • 14
  • 19
  • 9
    Note that NSViews do not always have a layer, in which case this does nothing. See Ioretoparisi's answer. – Kalle Sep 05 '13 at 14:40
  • 3
    I spent much time trying to perform this code in viewDidLoad method of my controller (for self.myCustomView). I figured out that you should use viewWillAppear method instead. – surfrider Apr 24 '15 at 13:24
22

edit/update: Xcode 8.3.1 • Swift 3.1

extension NSView {
    var backgroundColor: NSColor? {
        get {
            guard let color = layer?.backgroundColor else { return nil }
            return NSColor(cgColor: color)
        }
        set {
            wantsLayer = true
            layer?.backgroundColor = newValue?.cgColor
        }
    }
}

usage:

let myView = NSView(frame: NSRect(x: 0, y: 0, width: 100, height: 100))
print(myView.backgroundColor ?? "none")     //  NSView's background hasn't been set yet = nil
myView.backgroundColor = .red               // set NSView's background color to red color
print(myView.backgroundColor ?? "none")
view.addSubview(myView)
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • I think this is the cleanest solution, but am concerned about the statement the Objc.io folks made here: https://www.objc.io/issues/14-mac/appkit-for-uikit-developers/ - "...you should not try to interact with the layers directly, as AppKit owns those layers.". I'm not sure what could go wrong. Either way, I'm adopting this solution in my own code. – Kevin Sliech Jan 16 '16 at 00:20
7

Best Solution :

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        self.wantsLayer = YES;
    }
    return self;
}

- (void)awakeFromNib
{
    float r = (rand() % 255) / 255.0f;
    float g = (rand() % 255) / 255.0f;
    float b = (rand() % 255) / 255.0f;

    if(self.layer)
    {
        CGColorRef color = CGColorCreateGenericRGB(r, g, b, 1.0f);
        self.layer.backgroundColor = color;
        CGColorRelease(color);
    }
}
Nagaraj
  • 802
  • 9
  • 11
  • Hehe. That would work if you like to have a random background color every time you launch, though a little more complicated then doing the same with draw rect (accepted answer) – BadPirate Sep 30 '13 at 17:39
  • If it is just a matter of drawing a BG colour then what is the necessary of overriding the "drawRect:" – Nagaraj Oct 03 '13 at 04:29
  • Yeah, the top answer http://stackoverflow.com/a/2962882/285694 gives that response. My question actually required to have an alpha channel, so I gave this one the check - http://stackoverflow.com/a/2962839/285694 - Technically this question is about setting the background on an NSView with an Alpha channel (like you can with NSWindow) -- But I think a lot of people come here looking for just how to set the color (thus the first answer is more popular). – BadPirate Oct 03 '13 at 16:41
7

Just set backgroundColor on the layer (after making the view layer backed).

view.wantsLayer = true
view.layer?.backgroundColor = CGColor.white
Geri Borbás
  • 15,810
  • 18
  • 109
  • 172
  • Would that work on MacOS standard NSView? Can I avoid subclassing? and at what point should I apply the wantsLayer and layer.backgroundColor? in some "awakeFromNib" ? which one? – Motti Shneor Aug 10 '21 at 09:38
  • I think you can set it on any instance without subclassing. I bet there are no restrictions regarding when to use it either. Put it where you want your view to be colored. Be it on awake, after a timer fires, or upon the press of a button, whatever have you. – Geri Borbás Aug 10 '21 at 12:35
6

In Swift:

override func drawRect(dirtyRect: NSRect) {

    NSColor.greenColor().setFill()
    NSRectFill(dirtyRect)

    super.drawRect(dirtyRect)
}
User
  • 31,811
  • 40
  • 131
  • 232
6

Use NSBox, which is a subclass of NSView, allowing us to easily style

Swift 3

let box = NSBox()
box.boxType = .custom
box.fillColor = NSColor.red
box.cornerRadius = 5
onmyway133
  • 45,645
  • 31
  • 257
  • 263
6

Without doubt the easiest way, also compatible with Color Set Assets:

Swift:

view.setValue(NSColor.white, forKey: "backgroundColor")

Objective-C:

[view setValue: NSColor.whiteColor forKey: "backgroundColor"];

Interface Builder:

Add a user defined attribute backgroundColor in the interface builder, of type NSColor.

Ely
  • 8,259
  • 1
  • 54
  • 67
  • This relies on undocumented behaviour and should be avoided if you still want to work in future versions of AppKit. – nschmidt Jul 03 '20 at 09:23
5

I tested the following and it worked for me (in Swift):

view.wantsLayer = true
view.layer?.backgroundColor = NSColor.blackColor().colorWithAlphaComponent(0.5).CGColor
Tyler Liu
  • 19,552
  • 11
  • 100
  • 84
  • Did you put an image on top of you view after you did this? Or did you use an NSView instead of an NSImageView? – justColbs Jun 05 '15 at 13:26
3

In Swift 3, you can create an extension to do it:

extension NSView {
    func setBackgroundColor(_ color: NSColor) {
        wantsLayer = true
        layer?.backgroundColor = color.cgColor
    }
}

// how to use
btn.setBackgroundColor(NSColor.gray)
Kaiyuan Xu
  • 780
  • 7
  • 13
2

In swift you can subclass NSView and do this

class MyView:NSView {
    required init?(coder: NSCoder) {
        super.init(coder: coder);

        self.wantsLayer = true;
        self.layer?.backgroundColor = NSColor.redColor().CGColor;
    }
}
Chad Cache
  • 9,668
  • 3
  • 56
  • 48
2

This supports changing systemwide appearance (turning dark mode on or off) while the application is running. You can also set the background colour in Interface Builder, if you set the class of the view to BackgroundColorView first.


class BackgroundColorView: NSView {
    @IBInspectable var backgroundColor: NSColor? {
        didSet { needsDisplay = true }
    }

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        wantsLayer = true
    }

    required init?(coder decoder: NSCoder) {
        super.init(coder: decoder)
        wantsLayer = true
    }

    override var wantsUpdateLayer: Bool { return true }

    override func updateLayer() {
        layer?.backgroundColor = backgroundColor?.cgColor
    }
}
kareman
  • 711
  • 1
  • 6
  • 16
1

Have a look at RMSkinnedView. You can set the NSView's background color from within Interface Builder.

Raffael
  • 1,119
  • 10
  • 20
1

Just small reusable class (Swift 4.1)

class View: NSView {

   var backgroundColor: NSColor?

   convenience init() {
      self.init(frame: NSRect())
   }

   override func draw(_ dirtyRect: NSRect) {
      if let backgroundColor = backgroundColor {
         backgroundColor.setFill()
         dirtyRect.fill()
      } else {
         super.draw(dirtyRect)
      }
   }
}

// Usage
let view = View()
view.backgroundColor = .white
Vlad
  • 6,402
  • 1
  • 60
  • 74