Best way to change the background color for an NSView
Asked Answered
O

18

163

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.

Orourke answered 3/6, 2010 at 1:25 Comment(0)
S
144

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()
    }

}
Sunflower answered 3/6, 2010 at 1:55 Comment(7)
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 developer.apple.com/mac/library/documentation/Cocoa/Reference/… with the NSCompositeSourceOver operation.Licastro
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.Orourke
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.Sunflower
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 ?Sussi
@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.Orourke
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.Kashgar
I found in XCode 15 on Sonoma, I had to draw the background on the full self.frame (with origin set to .zero) instead of only the dirtyRect. Otherwise, it would completely break the UI of any subviews.Antipas
M
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!

Mohur answered 3/10, 2011 at 12:49 Comment(3)
Careful: calling setLayer: or setWantsLayer: breaks any WebViews you might have in your application in very creative ways! (Even in different windows...)Epley
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.Liebig
You shouldn't set the layer but can still achieve this. See ColoredView at objc.io/issues/14-mac/appkit-for-uikit-developersClywd
A
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

Amphicoelous answered 4/3, 2016 at 7:5 Comment(6)
Using NSBox worked great for me. Switching to a layer-backed view was causing redraw issues, whereas the box did not.Camara
This is an excellent and simple solution that should be in the docsColquitt
Does this color NSPopover triangles though?Embattled
i regret only that i have but one upvote to give this answer.Postobit
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.Poseidon
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.Poseidon
S
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]];
Strapless answered 15/1, 2015 at 0:10 Comment(0)
O
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));
}
Orourke answered 3/6, 2010 at 1:41 Comment(4)
Thanks. I needed to convert the dirtyRect to a CGRect. Ammended last line to CGContextFillRect(context, NSRectToCGRect(dirtyRect)); Can you edit your answer?Crackup
They used to be toll free bridged :/Orourke
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.Bocanegra
using drawRect results in added draw time, etc. CALayer is a better solution in 10.8 and up.Usurer
H
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);
Helbonnah answered 26/4, 2013 at 1:39 Comment(2)
Note that NSViews do not always have a layer, in which case this does nothing. See Ioretoparisi's answer.Lizliza
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.Wakeup
R
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)
Ringe answered 3/7, 2015 at 21:40 Comment(1)
I think this is the cleanest solution, but am concerned about the statement the Objc.io folks made here: 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.Quaternary
C
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);
    }
}
Cornemuse answered 30/9, 2013 at 9:52 Comment(3)
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)Orourke
If it is just a matter of drawing a BG colour then what is the necessary of overriding the "drawRect:"Cornemuse
Yeah, the top answer https://mcmap.net/q/149536/-best-way-to-change-the-background-color-for-an-nsview gives that response. My question actually required to have an alpha channel, so I gave this one the check - https://mcmap.net/q/149536/-best-way-to-change-the-background-color-for-an-nsview - 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).Orourke
B
7

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

view.wantsLayer = true
view.layer?.backgroundColor = CGColor.white
Bridgid answered 26/1, 2020 at 17:43 Comment(2)
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?Poseidon
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.Tussis
C
6

In Swift:

override func drawRect(dirtyRect: NSRect) {

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

    super.drawRect(dirtyRect)
}
Centner answered 5/4, 2015 at 9:51 Comment(0)
H
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
Heather answered 6/3, 2017 at 22:31 Comment(1)
NSBox does not help in case of NSPopover since it also has the triangle part.Lillith
B
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.

Bifurcate answered 8/11, 2018 at 10:34 Comment(1)
This relies on undocumented behaviour and should be avoided if you still want to work in future versions of AppKit.Herbst
P
5

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

view.wantsLayer = true
view.layer?.backgroundColor = NSColor.blackColor().colorWithAlphaComponent(0.5).CGColor
Pyre answered 17/5, 2015 at 1:55 Comment(1)
Did you put an image on top of you view after you did this? Or did you use an NSView instead of an NSImageView?Dermot
C
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)
Cultured answered 11/12, 2016 at 5:49 Comment(0)
M
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;
    }
}
Monzon answered 21/8, 2015 at 6:45 Comment(1)
Swift 4.2 Xcode 10 self.layer?.backgroundColor = NSColor.red.cgColorDesrosiers
A
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
    }
}
Apfel answered 9/3, 2019 at 22:46 Comment(0)
A
1

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

Angary answered 8/8, 2013 at 15:7 Comment(0)
B
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
Brana answered 29/1, 2018 at 21:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.