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)
}
}
Related
I setup UILabel appearance in my app delegate using:
UILabel.appearance().textColor = UIColor.white
I also have a custom UIView subclass that contains a UILabel along with some other elements (omitted here):
#IBDesignable
class CustomView: UIView {
private let descriptionLabel = HCLabel()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
private func setup() {
self.descriptionLabel.textColor = UIColor.black
// ... other things not related to descriptionLabel
}
}
If I instantiate CustomView in a storyboard, everything works just fine. If, however, I instantiate it in code, the descriptionLabel is white (appearance color), not black (the color I set). What's going on here? The way I understood it was that if I set a custom color, the appearance color will not be used.
What you're experiencing is simply a matter of the exact timing with which the UIAppearance proxy applies its settings to a new UIView. When are we to suppose it does this? It can't possibly do it before init, because init is the first thing that happens in the life of the UIView. Thus, the order of events is like this:
override init(frame: CGRect) {
super.init(frame: frame)
setup() // black
}
// and some time later, UIAppearance proxy comes along and sets it to white
So your goal is to call setup pretty early in the life of the label — and certainly before the user ever has a chance to see it — but not so early that the UIAppearance proxy acts later. Let's move the call to setup to a bit later in the life of the label:
// some time earlier, UIAppearance proxy sets it to white
override func didMoveToSuperview() {
setup() // black
}
Now we're acting after the appearance proxy has had a chance to act, and so your settings are the last to operate, and they win the day.
We remain in ignorance of how early we could move the call to setup and still come along after the appearance proxy setting has been obeyed. If you have time, you might like to experiment with that. For example, willMoveToSuperview is earlier; if you call setup there (and not in didMoveToSuperview), does that work? Play around and find out!
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 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.
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