I'm using the "Alternating Rows" option in Interface Builder to get alternating row colors on an NSTableView. Is there any way to change the colors of the alternating rows?
Found a better way to do it here. That method overrides the highlightSelectionInClipRect: method in an NSTableView subclass so you can use any color you want for the alternating rows. It's not as hackish as using an NSColor category, and it only affects table views you choose.
If you want to use an undocumented way, make a NSColor
category and override _blueAlternatingRowColor
like this:
@implementation NSColor (ColorChangingFun)
+(NSColor*)_blueAlternatingRowColor
{
return [NSColor redColor];
}
@end
or to change both colors, override controlAlternatingRowBackgroundColors
to return an array of colors you want alternated.
@implementation NSColor (ColorChangingFun)
+(NSArray*)controlAlternatingRowBackgroundColors
{
return [NSArray arrayWithObjects:[NSColor redColor], [NSColor greenColor], nil];
}
@end
Found a better way to do it here. That method overrides the highlightSelectionInClipRect: method in an NSTableView subclass so you can use any color you want for the alternating rows. It's not as hackish as using an NSColor category, and it only affects table views you choose.
I subclassed NSTableView and implemented drawRow:clipRect: like this...
- (void)drawRow:(NSInteger)row clipRect:(NSRect)clipRect
{
NSColor *color = (row % 2) ? [NSColor redColor] : [NSColor whiteColor];
[color setFill];
NSRectFill([self rectOfRow:row]);
[super drawRow:row clipRect:clipRect];
}
It seems to work, but it's so simple that I'm wondering if I'm missing something.
if (row != [self selectedRow])
to prevent issues with the highlighted background of selected rows. –
Disrespect I wanted a solution that worked just like the regular NSTableView
, including support for elastic scrolling and such, so I created an NSTableView
subclass that has an NSColor*
property called alternateBackgroundColor
, and then overrode the -drawBackgroundColorInClipRect:
method like so:
- (void) drawBackgroundInClipRect:(NSRect)clipRect {
if([self alternateBackgroundColor] == nil) {
// If we didn't set the alternate colour, fall back to the default behaviour
[super drawBackgroundInClipRect:clipRect];
} else {
// Fill in the background colour
[[self backgroundColor] set];
NSRectFill(clipRect);
// Check if we should be drawing alternating coloured rows
if([self alternateBackgroundColor] && [self usesAlternatingRowBackgroundColors]) {
// Set the alternating background colour
[[self alternateBackgroundColor] set];
// Go through all of the intersected rows and draw their rects
NSRect checkRect = [self bounds];
checkRect.origin.y = clipRect.origin.y;
checkRect.size.height = clipRect.size.height;
NSRange rowsToDraw = [self rowsInRect:checkRect];
NSUInteger curRow = rowsToDraw.location;
while(curRow < rowsToDraw.location + rowsToDraw.length) {
if(curRow % 2 != 0) {
// This is an alternate row
NSRect rowRect = [self rectOfRow:curRow];
rowRect.origin.x = clipRect.origin.x;
rowRect.size.width = clipRect.size.width;
NSRectFill(rowRect);
}
curRow++;
}
// Figure out the height of "off the table" rows
CGFloat rowHeight = [self rowHeight];
if( ([self gridStyleMask] & NSTableViewSolidHorizontalGridLineMask) == NSTableViewSolidHorizontalGridLineMask
|| ([self gridStyleMask] & NSTableViewDashedHorizontalGridLineMask) == NSTableViewDashedHorizontalGridLineMask) {
rowHeight += 2.0f; // Compensate for a grid
}
// Draw fake rows below the table's last row
CGFloat virtualRowOrigin = 0.0f;
NSInteger virtualRowNumber = [self numberOfRows];
if([self numberOfRows] > 0) {
NSRect finalRect = [self rectOfRow:[self numberOfRows]-1];
virtualRowOrigin = finalRect.origin.y + finalRect.size.height;
}
while(virtualRowOrigin < clipRect.origin.y + clipRect.size.height) {
if(virtualRowNumber % 2 != 0) {
// This is an alternate row
NSRect virtualRowRect = NSMakeRect(clipRect.origin.x,virtualRowOrigin,clipRect.size.width,rowHeight);
NSRectFill(virtualRowRect);
}
virtualRowNumber++;
virtualRowOrigin += rowHeight;
}
// Draw fake rows above the table's first row
virtualRowOrigin = -1 * rowHeight;
virtualRowNumber = -1;
while(virtualRowOrigin + rowHeight > clipRect.origin.y) {
if(abs(virtualRowNumber) % 2 != 0) {
// This is an alternate row
NSRect virtualRowRect = NSMakeRect(clipRect.origin.x,virtualRowOrigin,clipRect.size.width,rowHeight);
NSRectFill(virtualRowRect);
}
virtualRowNumber--;
virtualRowOrigin -= rowHeight;
}
}
}
}
I'm not sure how recently this was added, or if it is as flexible as you need it to be, but I noticed that you can specify "Alternating" rows in Interface Builder in Xcode 4.6 (and possibly earlier).
- Open your nib in Xcode and select your
NSTableView
orNSOutlineView
- Show the Attributes Inspector in the Utilities Pane (⎇⌘4)
- Notice the
Highlight Alternating Rows
checkbox.
There is no settable property for this, however you can respond to the delegate method -tableView:willDisplayCell:forTableColumn:row:
and set the cell's background color based on the evenness of the row number.
Nate Thorn's answer worked perfectly for me.
Here it is, refactored for Swift:
import Foundation
import Cocoa
import AppKit
public class SubclassedTableView : NSTableView {
private func
alternateBackgroundColor() -> NSColor? {
return NSColor.redColor() // Return any color you like
}
public override func
drawBackgroundInClipRect(clipRect: NSRect) {
if alternateBackgroundColor() == nil {
// If we didn't set the alternate colour, fall back to the default behaviour
super.drawBackgroundInClipRect(clipRect)
} else {
// Fill in the background colour
self.backgroundColor.set()
NSRectFill(clipRect)
// Check if we should be drawing alternating coloured rows
if usesAlternatingRowBackgroundColors {
// Set the alternating background colour
alternateBackgroundColor()!.set()
// Go through all of the intersected rows and draw their rects
var checkRect = bounds
checkRect.origin.y = clipRect.origin.y
checkRect.size.height = clipRect.size.height
let rowsToDraw = rowsInRect(checkRect)
var curRow = rowsToDraw.location
repeat {
if curRow % 2 != 0 {
// This is an alternate row
var rowRect = rectOfRow(curRow)
rowRect.origin.x = clipRect.origin.x
rowRect.size.width = clipRect.size.width
NSRectFill(rowRect)
}
curRow++
} while curRow < rowsToDraw.location + rowsToDraw.length
// Figure out the height of "off the table" rows
var thisRowHeight = rowHeight
if gridStyleMask.contains(NSTableViewGridLineStyle.SolidHorizontalGridLineMask)
|| gridStyleMask.contains(NSTableViewGridLineStyle.DashedHorizontalGridLineMask) {
thisRowHeight += 2.0 // Compensate for a grid
}
// Draw fake rows below the table's last row
var virtualRowOrigin = 0.0 as CGFloat
var virtualRowNumber = numberOfRows
if numberOfRows > 0 {
let finalRect = rectOfRow(numberOfRows-1)
virtualRowOrigin = finalRect.origin.y + finalRect.size.height
}
repeat {
if virtualRowNumber % 2 != 0 {
// This is an alternate row
let virtualRowRect = NSRect(x: clipRect.origin.x, y: virtualRowOrigin, width: clipRect.size.width, height: thisRowHeight)
NSRectFill(virtualRowRect)
}
virtualRowNumber++
virtualRowOrigin += thisRowHeight
} while virtualRowOrigin < clipRect.origin.y + clipRect.size.height
// Draw fake rows above the table's first row
virtualRowOrigin = -1 * thisRowHeight
virtualRowNumber = -1
repeat {
if abs(virtualRowNumber) % 2 != 0 {
// This is an alternate row
let virtualRowRect = NSRect(x: clipRect.origin.x, y: virtualRowOrigin, width: clipRect.size.width, height: thisRowHeight)
NSRectFill(virtualRowRect)
}
virtualRowNumber--
virtualRowOrigin -= thisRowHeight
} while virtualRowOrigin + thisRowHeight > clipRect.origin.y
}
}
}
}
Swift 4+ version of AlternateRealist's answer:
public class AlternateBgColorTableView: NSTableView {
var alternateBackgroundColor: NSColor? = .red
override public func drawBackground(inClipRect clipRect: NSRect) {
// If we didn't set the alternate color, fall back to the default behavior
guard let alternateBackgroundColor = alternateBackgroundColor else {
super.drawBackground(inClipRect: clipRect)
return
}
// Fill in the background color
backgroundColor.set()
clipRect.fill()
// Check if we should be drawing alternating colored rows
if usesAlternatingRowBackgroundColors {
// Set the alternating background color
alternateBackgroundColor.set()
// Go through all of the intersected rows and draw their rects
var checkRect = bounds
checkRect.origin.y = clipRect.origin.y
checkRect.size.height = clipRect.height
let rowsToDraw = rows(in: checkRect)
var currentRow = rowsToDraw.location
repeat {
if currentRow % 2 != 0 {
// This is an alternate row
var rowRect = rect(ofRow: currentRow)
rowRect.origin.x = clipRect.origin.x
rowRect.size.width = clipRect.width
rowRect.fill()
}
currentRow += 1
} while currentRow < rowsToDraw.location + rowsToDraw.length
// Figure out the height of "off the table" rows
var thisRowHeight = rowHeight
if gridStyleMask.contains(.solidHorizontalGridLineMask) || gridStyleMask.contains(.dashedHorizontalGridLineMask) {
thisRowHeight += 2 // Compensate for a grid
}
// Draw fake rows below the table's last row
var virtualRowOrigin: CGFloat = 0
var virtualRowNumber = numberOfRows
if numberOfRows > 0 {
let finalRect = rect(ofRow: numberOfRows - 1)
virtualRowOrigin = finalRect.origin.y + finalRect.height
}
repeat {
if virtualRowNumber % 2 != 0 {
// This is an alternate row
let virtualRowRect = NSRect(x: clipRect.origin.x, y: virtualRowOrigin, width: clipRect.width, height: thisRowHeight)
virtualRowRect.fill()
}
virtualRowNumber += 1
virtualRowOrigin += thisRowHeight
} while virtualRowOrigin < clipRect.origin.y + clipRect.size.height
// Draw fake rows above the table's first row
virtualRowOrigin = -1 * thisRowHeight
virtualRowNumber = -1
repeat {
if abs(virtualRowNumber) % 2 != 0 {
// This is an alternate row
let virtualRowRect = NSRect(x: clipRect.origin.x, y: virtualRowOrigin, width: clipRect.width, height: thisRowHeight)
virtualRowRect.fill()
}
virtualRowNumber -= 1
virtualRowOrigin -= thisRowHeight
} while virtualRowOrigin + thisRowHeight > clipRect.origin.y
}
}
}
If you wish to set a custom color paired with the data in each row, I found this way of doing so, thanks to the posts here above, and this solution is also working to implement a custom alternate row color ( except for the empty rows ) :
create a subclass of NSTableView :
ColoredRowTableView.h
@protocol ColoredRowTableViewDelegate <NSTableViewDelegate>
-(NSColor *)colorForRow:(NSInteger)row;
@end
@interface ColoredRowTableView : NSTableView
@end
in ColoredRowTableView.m
- (void)drawRow:(NSInteger)row clipRect:(NSRect)clipRect
{
if (row == [self selectedRow])
{
[super drawRow:row clipRect:clipRect];
return;
}
NSColor *color = [(id<ColoredRowTableViewDelegate>)[self delegate] colorForRow:row];
[color setFill];
NSRectFill([self rectOfRow:row]);
[super drawRow:row clipRect:clipRect];
}
then in your table view delegate :
#import "ColoredRowTableView.h"
@interface MyTableViewDelegate : NSViewController <ColoredRowTableViewDelegate>
// instead of <NSTableViewDelegate>
implement the colorForRow: method in your delegate. If the goal is only to provide alternate row it can return a color based on the row number, if it is even or odd. Also don't forget to change the class of your table view to ColoredRowTableView in interface builder
© 2022 - 2024 — McMap. All rights reserved.