Reputation: 1204
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.
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?
Upvotes: 24
Views: 29742
Reputation: 190
There is a drawback when using a backgroundColor property of NSView's layer. If appearance did change, cgColor do not change. According to a suggestion, use NSBox's fillColor property. For example subclass it.
import AppKit
class BasicView: NSBox {
// MARK: - Properties
var backgroundColor: NSColor {
get { fillColor }
set { fillColor = newValue }
}
// MARK: - Lifecycle
init() {
super.init(frame: .zero)
configureView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Methods
private func configureView() {
boxType = .custom
contentViewMargins = .zero
borderWidth = 0
borderColor = .clear
}
}
But I think it is much better to use CALayer backed NSView:
import AppKit
final class BasicView: NSView {
// MARK: - Properties
var backgroundColor: NSColor? {
didSet { configureBackground() }
}
override var wantsUpdateLayer: Bool {
get { true }
}
// MARK: - Lifecycle
init() {
super.init(frame: .zero)
configureView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Methods
override func updateLayer() {
super.updateLayer()
configureBackground()
}
private func configureView() {
wantsLayer = true
layerContentsRedrawPolicy = .onSetNeedsDisplay
}
private func configureBackground() {
guard let layer else {
return
}
if let backgroundColor {
layer.backgroundColor = backgroundColor.cgColor
} else {
layer.backgroundColor = nil
}
}
}
Upvotes: 0
Reputation: 5566
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.
titlePosition
to None
boxType
to Custom
borderType
to None
fillColor
from asset catalogue (IMPORTANT for dark mode)borderWidth
and borderRadius
to 0
Bonus:
dynamic
+ no need to override animation(forKey key:NSAnimatablePropertyKey) ->
)WARNING:
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
}
}
Upvotes: 1
Reputation: 9121
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
.
Upvotes: 3
Reputation: 523
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.
}
}}
Upvotes: 38
Reputation: 236260
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:
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
Upvotes: 16
Reputation: 1790
Best way to set a NSView background colour in MacOS 10.14 with dark mode support :
1/ Create your colour in Assets.xcassets
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
Upvotes: 5
Reputation: 560
Since macOS 10.13 (High Sierra) NSView responds to selector backgroundColor although it is not documented!
Upvotes: 0
Reputation: 2331
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
}
}
}
}
Upvotes: 1
Reputation: 5086
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
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
}
}
}
Upvotes: 33
Reputation: 1204
This works a lot better:
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.blueColor().setFill()
NSRectFill(dirtyRect);
}
Upvotes: 5