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.
Related
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 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 have one big CGRect and two small CGRect inside. I want to draw the big CGRect in red and to form two transparent holes corresponding to the small CGRect.
I am not able to do it. I have tried to use NSBezierPath but in macOS there is no method NSBezierPath.CGPath like in UIBezierPath for iOS.
You don't have to use Core Graphics. You can create a NSView subclass and just stroke/fill the path in draw(_:). In Swift 3:
class HolyView: NSView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let path = ... // build the `NSBezierPath` however you want
NSColor.blue.setFill()
path.fill()
}
}
You can then add that view programmatically, or you can make it #IBDesignable and add it directly on your storyboard.
I am trying to create a custom shape NSButton. In particular I am trying to make a round button, using a custom image. I've found a tutorial on the creation of custom UIButton and tried to adapt it to NSButton. But there's a huge problem. clipsToBounds seems to be iOS only(
Here's my code:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var mainButton: NSButton!
var size = 32
override func viewDidLoad() {
super.viewDidLoad()
configureButton()
// Do any additional setup after loading the view.
}
func configureButton()
{
mainButton.wantsLayer = true
mainButton.layerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay
mainButton.layer?.cornerRadius = 0.5 * mainButton.bounds.size.width
mainButton.layer?.borderColor = NSColor(red:0.0/255.0, green:122.0/255.0, blue:255.0/255.0, alpha:1).CGColor as CGColorRef
mainButton.layer?.borderWidth = 2.0
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
What am I doing wrong? How can I make a circular NSButton? Can you suggest anything on replacing clipsToBounds?
Because here is what I was able to get so far:
NSButton is a subclass of NSView, so all methods in NSView, such as drawRect(_:), are also available in NSButton.
So create a new Button.swift which you draw your custom layout
import Cocoa
class Button: NSButton {
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
// Drawing code here.
let path = NSBezierPath(ovalIn: dirtyRect)
NSColor.green.setFill()
path.fill()
}
}
Great Tutorial Here. It is iOS , but quite similar!
Don't play around with corner radius. A circle doesn't have corners. To make the button appear as a circle, mask the button's layer to a circle.
You are setting the radius based upon the width of the button. By the look of your screenshot, the button is not a square when you start, so rounding the corners cannot create a circle.