22
votes

What is the process of drawing to NSView using storyboards for osx? I have added a NSView to the NSViewController. Then, I added a few constraints and an outlet. enter image description here

Next, I added some code to change the color: import Cocoa

class ViewController: NSViewController {

    @IBOutlet var box: NSView!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear() {
        box.layer?.backgroundColor = NSColor.blueColor().CGColor
        //box.layer?.setNeedsDisplay()
    }

    override var representedObject: AnyObject? {
        didSet {
        // Update the view, if already loaded.
        }
    } 
}

I would like to do custom drawing and changing colors of the NSView. I have performed sophisticated drawing on iOS in the past, but am totally stuck here. Does anyone see what I'm doing wrong?

9

9 Answers

39
votes

The correct way is

class ViewController: NSViewController {

@IBOutlet var box: NSView!

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.wantsLayer = true

}

override func viewWillAppear() {
    box.layer?.backgroundColor = NSColor.blue.cgColor
    //box.layer?.setNeedsDisplay()
}

override var representedObject: AnyObject? {
    didSet {
    // Update the view, if already loaded.
    }
}}
32
votes

Swift via property

extension NSView {

    var backgroundColor: NSColor? {
        get {
            if let colorRef = self.layer?.backgroundColor {
                return NSColor(CGColor: colorRef)
            } else {
                return nil
            }
        }
        set {
            self.wantsLayer = true
            self.layer?.backgroundColor = newValue?.CGColor
        }
    }   
}

Usage:

    yourView.backgroundColor = NSColor.greenColor()

Where yourView is NSView or any of its subclasses

Updated for Swift 3

extension NSView {

    var backgroundColor: NSColor? {

        get {
            if let colorRef = self.layer?.backgroundColor {
                return NSColor(cgColor: colorRef)
            } else {
                return nil
            }
        }

        set {
            self.wantsLayer = true
            self.layer?.backgroundColor = newValue?.cgColor
        }
    }
}
16
votes

edit/update:

Another option is to design your own colored view:

import Cocoa
@IBDesignable class ColoredView: NSView {
    @IBInspectable var backgroundColor: NSColor = .clear
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
        backgroundColor.set()
        dirtyRect.fill()
    }
}

Then you just need to add a Custom View NSView and set the custom class in the inspector:

Custom View

ColoredView

IBInspectable


Original Answer

Swift 3.0 or later

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

let myView = NSView(frame: NSRect(x: 0, y: 0, width: 100, height: 100))
myView.backgroundColor = .red
5
votes

This works a lot better:

    override func drawRect(dirtyRect: NSRect) {
        super.drawRect(dirtyRect)
        NSColor.blueColor().setFill()
        NSRectFill(dirtyRect);
    }
2
votes

Best way to set a NSView background colour in MacOS 10.14 with dark mode support :

1/ Create your colour in Assets.xcassets enter image description here

2/ Subclass your NSView and add this :

class myView: NSView {
    override func updateLayer() {
       self.layer?.backgroundColor = NSColor(named: "customControlColor")?.cgColor
    }
}

Very simple and dark mode supported with the colour of your choice !

Full guide : https://developer.apple.com/documentation/appkit/supporting_dark_mode_in_your_interface

2
votes

Just one line of code is enough to change the background of any NSView object:

myView.setValue(NSColor.blue, forKey: "backgroundColor")

Instead of this, you can also add an user defined attribute in the interface designer of type Color with keyPath backgroundColor.

1
votes

Update to Swift 3 solution by @CryingHippo (It showed colors not on every run in my case). I've added DispatchQueue.main.async and now it shows colors on every run of the app.

extension NSView {

    var backgroundColor: NSColor? {

        get {
            if let colorRef = self.layer?.backgroundColor {
                return NSColor(cgColor: colorRef)
            } else {
                return nil
            }
        }

        set {

            DispatchQueue.main.async { 

                self.wantsLayer = true
                self.layer?.backgroundColor = newValue?.cgColor

            }

        }
    }
}
0
votes

Since macOS 10.13 (High Sierra) NSView responds to selector backgroundColor although it is not documented!

0
votes

None of the solutions is using pure power of Cocoa framework. The correct solution is to use NSBox instead of NSView. It has always supported fillColor, borderColor etc.

  1. Set titlePosition to None
  2. Set boxType to Custom
  3. Set borderType to None
  4. Set your desired fillColor from asset catalogue (IMPORTANT for dark mode)
  5. Set borderWidth and borderRadius to 0

Bonus:

  • it dynamically reacts to sudden change of appearance (light to dark)
  • it supports animations ( no need for dynamic + no need to override animation(forKey key:NSAnimatablePropertyKey) ->)
  • future macOS support automatically

WARNING:

  • Using NSBox + system colors in dark mode will apply tinting corectly. If you do not want tinting use catalogue color.

enter image description here

Alternative is to provide subclass of NSView and do the drawing updates in updateLayer

import Cocoa

@IBDesignable
class CustomView: NSView {

    @IBInspectable var backgroundColor : NSColor? {
        didSet { needsDisplay = true }
    }
    override var wantsUpdateLayer: Bool {
        return true
    }

    override func updateLayer() {
        guard let layer = layer else { return }
        layer.backgroundColor = backgroundColor?.cgColor
    }
}

Tinting: enter image description here