I tried to set the background color of an NSGridView by subclassing it and overriding its draw method like this:
class GridViewGreen: NSGridView
{ override func draw(_ dirtyRect: NSRect)
{ super.draw(dirtyRect)
let color = NSColor.green
let bp = NSBezierPath(rect: dirtyRect)
color.set()
bp.stroke()
print("drawing GridViewGreen")
}
}
But the draw method is never called.
NSGridViewis a lightweight component, like NSStackView, for layout only. Because of this it doesn't draw.
Just put the NSGridView into an NSBox and set its fillColor.
Update: Preferably take catlan's answer if possible. He is right in that NSGridView isn't really meant for rendering and this approach will more or less force it down that path.
At this point, pretty much every Cocoa application should be layer-backing their views and NSGridView and NSStackView aren't any different. Simply set the background color on the layer.
let gridView = NSGridView(views: [[view1, view2]])
gridView.wantsLayer = true
gridView.layer?.backgroundColor = NSColor.red.cgColor
NSGridView is a subclass of NSView so it inherits all of NSView's properties and methods including drawing - as seen in the draw(_ dirtyRect: NSRect) function. Make sure to include an IBOutlet in your view controller or change the NSGridView class in Interface Builder to GridViewGreen.
class GridViewGreen: NSGridView {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
NSColor.green.setFill()
dirtyRect.fill()
print("drawing GridViewGreen")
}
}
Basically your view controller doesn't know that you subclassed your grid view.
Related
I'm pretty new to coding. Im not sure if an IBOutlet (button, text field, etc) ctrl-dragged from a xib should go in the xib's NSView class or in the view controller which has the NSView added as a subview.
I've been playing around with this for a while, learning as I go. I'm stuck on wondering if I have the code structured correctly. This is for MacOS so resources are limited and often dated. I'd assume that an outlet added for a button, for example, would go in the controller as views should be "dumb". If I try that the actions always have "action" set automatically and type as Any as a default - not what I'm used to seeing. I suspect this may have something to do with the class set for the file's owner and the class set for the view in IB. If anyone can outline the best way to handle this that would be fantastic, thank you!
The view that loads the xib:
class View4: NSView {
#IBOutlet weak var view: View4!
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
Bundle.main.loadNibNamed("View4", owner: self, topLevelObjects: nil)
self.frame = self.bounds
self.wantsLayer = true
self.translatesAutoresizingMaskIntoConstraints = false
self.layer?.backgroundColor = NSColor.purple.cgColor
self.roundedCorners(on: self)
// add xib to custom NSView subclass
self.addSubview(self.view)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
}
The corresponding ViewController:
class View4Controller: NSViewController {
override func loadView() {
print("View4Controller.loadView")
self.view = NSView()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
print("View4Controller.viewDidLoad")
self.view = View4()
}
}
The idea of an outlet is to have a reference to an object that is outside of your code created. The concept is great for prototyping, but tends to become hard to manage as a project grow.
If you class is the class, then it can refer to itself. („self“ in swift or „this“ in c++) You don't need an outlet in this case.
The outlet is normally used by controller that need to maintain the view. The concept is a alternative to creating and configuring the view manually.
I've used Storyboard to set up the NSScrollView and I cannot find any option where I can disable the scroller's background. Any ideas on how to make this happen?
Basically idea behind this is subclass the NSScroller and then make changes accordingly.
I had the same requirement so I subclassed it and did the changes as shown.
objective c code is converted with help of online tool so apologies for mistakes
and have look.
this may help.
// Converted to Swift 4 by Swiftify v4.1.6766 - https://objectivec2swift.com/
// GridScroller.h
// Created by Vikram on 08/09/16.
import Cocoa
class OpaqGridScroller: NSScroller {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
NSColor.clear.setFill()
dirtyRect.fill()
// whatever style you want here for knob if you want
knobStyle = .dark
}
}
You can try override draw scroller rect
override func draw(_ dirtyRect: NSRect) {
NSDrawWindowBackground(bounds);
self.drawKnob()
}
You always can draw custom rect color by setting it before
override func draw(_ dirtyRect: NSRect) {
NSColor.clear.set()
__NSRectFill(dirtyRect)
self.drawKnob()
}
This solution worked for me.
class OpaqueGridScroller: NSScroller {
override func draw(_ dirtyRect: NSRect) {
// NSColor.clear.set()
// dirtyRect.fill()
self.drawKnob()
}
}
The foregoing answers that call self.drawKnob() are only partially correct. The problem is that the knob should usually not be drawn at all if there's nothing to scroll in that direction.
For my solution, I sub-classed the vertical NSScroller (scroll bar) in a scrolling NSTextView, in order to override the scroll bar's draw() method, as above. But when there was nothing to scroll, this draws a knob that has a length that's not correct.
So I added
#IBOutlet var myScroller : NSScrollView!
as a property of the sub-classed NSScroller and wired it up in the storyboard, so that the scroller bar can know the NSScrollView that uses it, and then draw the scroll bar with
override draw(_ dirtyRect: NSRect)
{
if myScroller.contentSize.height < myScroller.documentView!.bounds.height {
self.drawKnob()
}
}
The knob disappears when the NSScrollView is resized enough that there's nothing vertical to scroll, and re-appears when there is, while at the same time allowing whatever's in the background below it to show through.
I'm not sure if this is a hack, or the correct solution, but it seems to work as expected.
I have an NSOutlineView with custom NSTableViewRows used throughout.
I have overridden the draw method on the NSTableViewRow:
override func draw(_ dirtyRect: NSRect) {}
...so it should never draw anything. However, rows are occasionally solid black, and occasionally clear. I can't work out a pattern to when.
If I do put something in the draw function, it will be drawing over the black when it occurs, I can't seem to clear the black in the draw function, other than by filling with a solid colour.
To clear I have tried:
let context = NSGraphicsContext.current?.cgContext
context?.clear(dirtyRect)
and
NSColor.clear.setFill()
dirtyRect.fill(using: .copy)
If I look in the Debug View Hierarchy I can clearly see that it is the NSTableViewRow itself that is black.
I have tried setting wantsLayer and setting the backgroundColor of the layer in the draw function but that has no effect.
Can anyone explain where this black fill may be coming from and where it lives!
The only way I managed to ensure it wasn't there was to use:
override var wantsUpdateLayer: Bool { get { return true } }
...which suggests that NSTableViewRow is doing something a little weird.
(copied from comments now that we've discovered a workaround)
The documentation for NSTableRowView states that it "is responsible for displaying attributes associated with the row, including the selection highlight, and group row look." So the base row view class is clearly doing something, and the table view probably makes assumptions about it, and I would image it's tricky and highly optimized. :(
A workaround would be to call super.draw(dirtyRect) in your draw(_:NSRect) just to let the base NSTableRowView class do whatever internal magic it needs to do, and then erase whatever it has drawn and draw over that.
I actually thought of a possibly-less-hacky solution: Add an opaque subview to NSTableRowView that completely fills its bounds and draw whatever you're trying to draw in that subview.
Subclass NSTableRowView add override isOpaque
Swift:
override var isOpaque: Bool {
get {
return false
}
set {
}
}
Obj-C:
- (BOOL)isOpaque {
return NO;
}
Take in a consideration that there are a lot of drawings. Like:
override func drawBackground(in dirtyRect: NSRect) {
override func drawSelection(in dirtyRect: NSRect) {
override func drawSeparator(in dirtyRect: NSRect) {
and more...
I have a custom table row view that lets me set the background colour of the selected row:
import Cocoa
class MyRowView: NSTableRowView {
override func drawSelection(in dirtyRect: NSRect) {
NSColor.black.setFill()
dirtyRect.fill()
}
}
This works fine for any columns containing NSTextFields, where I have been able to set the background colour to transparent in the storyboard but for columns where I have an NSButton or an NSProgessIndicator, the background of those objects show the same as for unhighlighted rows. You can see in the image below that unselected rows are vibrant dark, showing what is underneath the window. Selected rows should be black, but the checkbox still has a vibrant dark look to it.
I have managed to solve this issue for the button with this answer, but this doesn't work for the progress indicator. i've tried subclassing NSProgressIndicator and just calling the superclass's init and draw methods, but it doesn't work:
class MyProgInd: NSProgressIndicator {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
}
}
I am setting some properties in the draw method of my UIImageView. However, these do not seem to be taking any affect at all. I see no rounded corners and no masking taking affect. The view is below:
//
// RoundImage.swift
//
//
import UIKit
class RoundImage: UIImageView {
//------------------
//MARK: - Setup and Initialization
//------------------
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
}
override func draw(_ rect: CGRect) {
super.draw(rect)
self.layer.cornerRadius = 30
self.layer.masksToBounds = true
self.clipsToBounds = true
}
//Setups content, styles, and defaults for the view
private func initialize(){
self.initStyle()
}
//Sets static content for the view
private func staticContent() {
}
//Styles the view's colors, borders, etc at initialization
private func initStyle(){
}
//Styles the view for variables that must be set at runtime
private func runtimeStyle(){
//TODO: These values cannot be changed in interface builder, but they should be able to be
}
//------------------
//MARK: - Interface Builder Methods
//------------------
//Sets the view up for interface builder with runtime styling and temp display values
override func prepareForInterfaceBuilder() {
self.runtimeStyle()
self.staticContent()
}
//------------------
//MARK: - Lifecycle Methods
//------------------
//Sets the view up with runtime styling and values
override func awakeFromNib() {
super.awakeFromNib()
self.runtimeStyle()
self.staticContent()
}
}
UIImageView when subclassed does not call the draw method at all. It is not allowed, although minimally documented. In this case, it is recommended to subclass UIView and then draw the image on the view in your draw method yourself.
For further information:
drawRect not being called in my subclass of UIImageView