Change NSTableView alternate row colors

2019-01-22 13:33发布

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?

7条回答
等我变得足够好
2楼-- · 2019-01-22 14:04

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).

  1. Open your nib in Xcode and select your NSTableView or NSOutlineView
  2. Show the Attributes Inspector in the Utilities Pane (⎇⌘4)
  3. Notice the Highlight Alternating Rows checkbox.

enter image description here

查看更多
趁早两清
3楼-- · 2019-01-22 14:04

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;
            }
        }
    }
}
查看更多
手持菜刀,她持情操
4楼-- · 2019-01-22 14:07

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.

查看更多
forever°为你锁心
5楼-- · 2019-01-22 14:18

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.

查看更多
小情绪 Triste *
6楼-- · 2019-01-22 14:24

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
            }
        }
    }
}
查看更多
来,给爷笑一个
7楼-- · 2019-01-22 14:25

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.

查看更多
登录 后发表回答