How to invert text colour of selected NSTableView row - xcode

I have an NSTableView in which I'm changing the colour of the text in a particular column, but when a row is selected, the text does not change to a more appropriate colour so that it's readable.
Finder's Date Modified, Size, and Kind columns have grey text, and when you select a file/folder row, the grey text changes to white (so that it's readable within the blue highlight).
I can't find a magic checkbox on XCode to enable this behaviour by default, so does anybody know how I might achieve the same effect?
I'm using Swift in XCode 6.3.
Thanks.

You don't say what view or view hierarchy you're using for your cells. You also don't say how or where you're setting the text fields' color or to what color, specifically.
When a row is selected, the row automatically computes its interiorBackgroundStyle. It also sets the backgroundStyle of the cell view if it responds to -setBackgroundStyle: or is an NSControl with a cell which responds to that.
If your cell view is an instance of NSTableCellView, it forwards the background style to all of its subviews which meet the same criteria. If you use a different container view as your cell view and you want the background style forwarded along like this, you would have to implement that yourself in your view class.
An NSTextField's cell (an NSTextFieldCell) responds to -setBackgroundStyle: and so has its background style set automatically by the above mechanisms. The text field cell will automatically change its text color to white if its textColor is one of the standard control colors (e.g. NSColor.controlTextColor()), but won't do so if you assign a non-standard color. So, if you're setting a specific color for your text, you are responsible for changing that when the background style changes.
You can use a subclass of NSTableCellView and add a property observer (didSet) for the backgroundStyle property. That can change the text field's textColor depending on the style that was set. For example, you can use your custom color if the background style is not .Dark or use the normal text field color NSColor.controlTextColor() if it is .Dark (so that the text field will actually display it as white).
You could also use a subclass of NSTextFieldCell for your text field and do the same sort of thing. Or override drawInteriorWithFrame(_:inView:) to draw with a different text color depending on the background style.

With the help of Ken's response (above) I was able to get it to work. Here's a rough draft that does what I want it to:
import Cocoa
class CustomTextFieldCell: NSTextFieldCell {
// When the background changes (as a result of selection/deselection) switch appropriate colours
override var backgroundStyle: NSBackgroundStyle {
didSet {
if (backgroundStyle == NSBackgroundStyle.Dark) {
if self.textColor == NSColor.redColor() {
self.textColor = NSColor.yellowColor()
}
} else if (backgroundStyle == NSBackgroundStyle.Light) {
if (self.textColor == NSColor.yellowColor()) {
self.textColor = NSColor.redColor()
}
}
}
}
// When the colour changes, switch to a better alternative for the cell's current background
override var textColor: NSColor? {
didSet {
if let colour = self.textColor {
if backgroundStyle == NSBackgroundStyle.Dark {
if self.textColor == NSColor.redColor() {
self.textColor = NSColor.yellowColor()
}
} else if backgroundStyle == NSBackgroundStyle.Light {
if (self.textColor == NSColor.yellowColor()) {
self.textColor = NSColor.redColor()
}
}
}
}
}
}
If I set my CustomTextFieldCell as the custom class for my table view cell in the identity inspector, it works. I needed to add a property observer for textColor as well so that rows which are currently highlighted get the same treatment. I may alter it now so that it's not hard coded but this demonstrates the concept.
Thanks Ken.

If you use system colors like NSColor.labelColor, the text color invertion will happen automatically for selected cells. If you want to use another color for unselected text, you can use code like this in your custom NSTableCellView class:
override var backgroundStyle: NSView.BackgroundStyle {
willSet {
textField.textColor = newValue == .emphasized ? .labelColor : .secondaryLabelColor
}
}
In this Swift example, the selected text color is .labelColor, otherwise it is .secondaryLabelColor.

Related

NSTableView selected row flickering while scrolling with arrow keys

I have a view based NSTableView and can't figure out how to work around a visual glitch where the currently selected row flickers while scrolling up or down with the arrow keys.
The selected row should appear 'glued' to either the top or bottom of the view, depending on scroll direction. The Finder shows this correct behavior in list view but a regular table view seems to not behave this way out of the box. I'm confused as to why that is and see no obvious way to circumvent it. Can anybody point me to possible causes / solutions?
Edit No. 1
A cell based NSTableView behaves in the desired way by default, so this is presumably a bug specific to the view based implementation. I don't want to use a cell based table for unrelated reasons though.
Edit No. 2
I've tried making the table view's parent view layer backed, as well as intercepting the up / down arrow keystrokes to do my own scrolling, but so far I haven't been able to eliminate the flickering.
Edit No. 3
I've created a small sample project that reproduces the issue.
It looks like the selection changes and the old and new selected rows are redrawn. Next the selected row is animated up/down. Disabling scroll animation fixes the issue. Scroll animation can be disabled by subclassing NSClipView and overriding scroll(to:).
override func scroll(to newOrigin: NSPoint) {
setBoundsOrigin(newOrigin)
}
It might have some side effects.
Edit
Copied from zrzka's solution, with some adjustments. Scroll animation is temporarily disabled when using the up arrow or down arrow key.
class TableView: NSTableView {
override func keyDown(with event: NSEvent) {
if let clipView = enclosingScrollView?.contentView as? ClipView,
(125...126).contains(event.keyCode) && // down arrow and up arrow
event.modifierFlags.intersection([.option, .shift]).isEmpty {
clipView.isScrollAnimationEnabled = false
super.keyDown(with: event)
clipView.isScrollAnimationEnabled = true
}
else {
super.keyDown(with: event)
}
}
}
class ClipView: NSClipView {
var isScrollAnimationEnabled: Bool = true
override func scroll(to newOrigin: NSPoint) {
if isScrollAnimationEnabled {
super.scroll(to: newOrigin)
} else {
setBoundsOrigin(newOrigin)
documentView?.enclosingScrollView?.flashScrollers()
}
}
}
Did you try change the view ?
scrollView.wantsLayer = true
If you used Interface Builder:
Select the scroll view
Open the View Effects Inspector (or press Cmd-Opt-8)
In the table, find the row for your scroll view and check the box.

Set UIBarButtonItem gray color when disabled

I have an extension for UIColor to get color from hex string. I'm using it as per below:
self.navigationItem.rightBarButtonItem?.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor(hexString: "#C0BFC0")], for: UIControlState.disabled)
self.navigationItem.rightBarButtonItem?.isEnabled = false
For some strange reason the color of the rightBarButtonItem is the same as before. Is there a way to change it when disabled? I have the above in my viewDidLoad function
I tried reading the below:
UIBarButtonItem is disabled, but has normal color
Change color of disabled bar button item in iOS
I'm able to change the color when it is not disabled. Seems when its disabled the colors are not obeyed?
when its disabled the colors are not obeyed?
I hit this bug with some toolbar items. My workaround is to ensure that the UIBarButtonItem title changes at runtime, when the disabled color should change. To do this, change the disabled color, then force the title to change by adding an invisble Unicode space if needed.
For example in Swift:
let zeroWidthSpaceStr = "\u{200B}"
func forceChangeItemTitle(_ item:UIBarButtonItem, newTitle:String) {
// Ensure the button item title is changed. Needed to pick up change in disabled text color
var newTitle = newTitle
if item.title == newTitle {
// Title already set, so change it invisibly
newTitle += zeroWidthSpaceStr
}
item.title = newTitle
}

NSTextView not changing font size or colour from NSFontPanel

I created a basic NSTextView, I selected the following options in Interface Builder:
Editable
Selectable
Field Editor
Rich Text
Undo
Graphics
Non-contiguous Layout
Font Panel
Ruler
Inspector Bar
I set the NSViewController to be the delegate of the NSTextView and the only other custom thing I've done for this NSTextView is to enable inserting tabs and new lines (by accepting First responder):
func textView(_ textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
if commandSelector == #selector(insertNewline(_:)) {
textView.insertNewlineIgnoringFieldEditor(self)
return true
} else if commandSelector == #selector(insertTab(_:)) {
textView.insertTabIgnoringFieldEditor(self)
return true
} //else if commandSelector == #selector(changeColor(_:)) {
//textView.setTextColor(NSFontPanel.colo, range: <#T##NSRange#>)
//}
return false
}
When I try to use the commands from the Font Panel + Inspector Bar, All the commands work fine except changing Font size or colour, is there anything that could be wrong? Or do I need to do extra binding/delegates, etc for this to work?
It is strange because if I change the Font itself (of a selected text) or the weight, it works fine (no coding was needed).
Update
I've found the root of the problem causing this. I'm displaying the TextView in a ViewController that is displayed using a Modal segue. If I change from Modal to Show, the size and colour work fine. There's also no need for the extra commands for insert new line and tab.
Is there any reason why this is the case? Is there any customisation that should be done to the segue to avoid this? And, why is the view controller presentation affecting the behaviour of the font panel?
NSFontPanel has a 'worksWhenModal' property which sounds as if it might be set to 'false'.
A Boolean that indicates whether the receiver allows fonts to be changed in modal windows and panels.
Documentation:

How to change the action of NSColorWell?

I can use NSColorWell as button to change the color of selected text. Since NSColorWell is object of an NSControl it has target and action. I guess, the action is implementing the code to change the color of the selected text in NSTextView. Where can I find this code for NSColorWell action? I would like to change it in away that I can use NSColorWell to change the background of the selected text, and ultimately to have in ToolBar two NSColorWell buttons: one to change text's foreground color and second one for text's background color.
NSColorWell is just a rectangular control to change a color.
You can either create an IBAction and connect it to the action of the color well in the Connections Inspector (⌥⌘6) of Interface Builder
#IBAction func changeColor(_ sender : NSColorWell)
{
let color = sender.color
// do something with the color
}
Or bind the value in Bindings Inspector (⌥⌘7) of Interface Builder to a dynamic property, this example will set the color well to a default value of green.
dynamic var color : NSColor = .green {
didSet {
// do something with the color
}
}

How to disable white text color in selection in view-based NSTableView?

I'm using a view-based table view and don't want it to draw NSTextFields with white text color when it is selected.
I was not able to find a working solution. So any help is very appreciated.
Here is my problem:
I want the "Selection is white" text also be drawn in the default text color.
So far I figured out that
Setting attributes in tableView:viewForTableColumn:item: does not really help
Setting NSTextField color to a custom color, which is something different than the control default color, will prevent from drawing in white but it still looses font style (bold, italic, etc).
Setting NSTableView's selectionHighlightStyle attribute to NSTableViewSelectionHighlightStyleNone does the trick but it will not redraw NSTableRowView. Also the select style is not what I want. I want the first click to select the row and the second click to edit the text field. When you use NSTableViewSelectionHighlightStyleNone your first click starts editing the text field.
The text color does not change if the NSTextField is bordered. But I don't want bordered text fields (As shown in the screenshot. The text fields are editable)
I couldn't figure out 'how' the text field gets the white color. I have overridden setTextColor: and realized that it is never called when selection is changed. So I guess an NSAttributedString is built somewhere inside the NSTableView drawing/selecting routine.
Any help is very much appreciated.
I found the answer. I had to subclass NSTableCellView and override setBackgroundStyle:. That's all!
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
[super setBackgroundStyle: NSBackgroundStyleLight];
}
Instead of overriding NSTableCellView's backgroundStyle, I found it more convenient to override viewWillDraw() in NSTableRowView instead. This is actually the method that by default changes your cell view's background style during selection.
You would disable this behavior by:
class TableViewDelegate: NSObject, NSTableViewDelegate {
func tableView(tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
return TableRowView(frame: NSRect.zero)
}
}
class TableRowView : NSTableRowView {
private override func viewWillDraw() {
// By do nothing we prevent the super method to be called. It would otherwise change the selected cell view's backgroundStyle property.
}
}
I set cell colour in my table view delegate's -tableView:willDisplayCell:forTableColumn:row: method.
-(void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell
forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
if(tableView==<table view id of interest>)
{
...
[cell setTextColor:<colour appropriate for this cell>];
...
}
...
}
This does not affect font size or styling.

Resources