问题
I've tried all found suggested solutions but ended up with this as the closest:
The target is to have custom color for:
- complete header background (e.g. green)
- text (e.g. white)
- sort control color (e.g. white)
Currently I can only set the interior bg and text color properly while leaving the header borders and sort controls in default white color.
I use the approach of custom NCTableHeaderCell
.
// <C> changing the bgColor doesn't work this way
[self.tv.headerView setWantsLayer:YES];
self.tv.headerView.layer.backgroundColor = NSColor.redColor.CGColor;
for (NSTableColumn *tc in self.tv.tableColumns) {
// <C> this only helps to change the header text color
tc.headerCell = [[NCTableHeaderCell_customizable alloc]initTextCell:@"Hdr"];
// <C> this changes the bgColor of the area of the headerCell label text (the interior) but it leaves border and sort controls in white color;
tc.headerCell.drawsBackground = YES;
tc.headerCell.backgroundColor = NSColor.greenColor;
// <C> changing the textColor doesn't work this way
// <SOLUTION> use NCTableHeaderCell_customizable as done above;
tc.headerCell.textColor = NSColor.redColor;
}
My custom class look like this:
@implementation NCTableHeaderCell_customizable
// <C> this works as expected
- (NSColor *) textColor
{
return NSColor.whiteColor;
}
// <C> this only sets the interior bgColor leaving the borders in standard color
//
//- (NSColor *) backgroundColor
//{
// return NSColor.redColor;
//}
- (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
// <C> this only sets the interior bgColor leaving the borders in standard color
//
//self.backgroundColor = NSColor.orangeColor;
[super drawWithFrame:cellFrame inView:controlView];
// <C> this draws the red bg as expected but doesn't show the interior;
//
// [NSColor.redColor set];
// NSRectFillUsingOperation(cellFrame, NSCompositingOperationSourceOver);
// <C> this draws the red bg as expected but
// 1) doesn't layout the interior well (I could fix this);
// 2) doesn't show the sort controls (it's over-drawn with the code bellow);
//
// [NSColor.redColor setFill];
// NSRectFill(cellFrame);
// CGRect titleRect = [self titleRectForBounds:cellFrame];
// [self.attributedStringValue drawInRect:titleRect];
}
- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (void) drawFocusRingMaskWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[super drawFocusRingMaskWithFrame:cellFrame inView:controlView];
}
- (void) drawSortIndicatorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView ascending:(BOOL)ascending priority:(NSInteger)priority;
{
[super drawSortIndicatorWithFrame:cellFrame inView:controlView ascending:ascending priority:priority];
//NSTableHeaderView *v = (NSTableHeaderView *)controlView;
}
I'm quite close to the solution but I don't know how to draw correctly the custom header cell to archive the goal.
回答1:
I don't find other ways to do it, just have to draw everything. Hope it's helpful.
CGRect outCellFrame;
- (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
// <C> this only sets the interior bgColor leaving the borders in standard color
outCellFrame = cellFrame;
[super drawWithFrame:cellFrame inView:controlView];
// <C> this draws the red bg as expected but doesn't show the interior;
//
// <C> this draws the red bg as expected but
// 1) doesn't layout the interior well (I could fix this);
// 2) doesn't show the sort controls (it's over-drawn with the code bellow);
//
// [NSColor.redColor setFill];
// NSRectFill(cellFrame);
// CGRect titleRect = [self titleRectForBounds:cellFrame];
// [self.attributedStringValue drawInRect:titleRect];
}
-(NSRect *) outer:(NSRect)rect fromInner: (NSRect)innerRect {
NSRect * list = (NSRect *) malloc(sizeof(rect) * 4);
NSRect rem;
NSDivideRect(rect, &list[0], &rem, innerRect.origin.x - rect.origin.x, NSRectEdgeMinX);
NSDivideRect(rect, &list[1], &rem, - innerRect.origin.x - innerRect.size.width + rect.origin.x + rect.size.width , NSRectEdgeMaxX);
NSDivideRect(rect, &list[2], &rem, innerRect.origin.y - rect.origin.y, NSRectEdgeMinY);
NSDivideRect(rect, &list[3], &rem, -innerRect.origin.y - innerRect.size.height + rect.origin.y + rect.size.height , NSRectEdgeMaxY);
return list;
}
-(void) updateBackground:(CGRect) cellFrame and:(CGRect) innerCellFrame{
[self.backgroundColor set];
NSRect * list = [self outer:cellFrame fromInner:innerCellFrame];
NSRectFillListUsingOperation(list, 4, NSCompositingOperationSourceOver);
free(list);}
- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[self updateBackground:outCellFrame and:cellFrame];
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (void) drawSortIndicatorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView ascending:(BOOL)ascending priority:(NSInteger)priority;
{
[self.backgroundColor set];
NSImage * image = ascending ? [NSImage imageNamed:@"NSDescendingSortIndicator"]:[NSImage imageNamed:@"NSAscendingSortIndicator"] ;
// use your image here. If you need to change color, try to make a colored templated image here.
CGRect frame = [self sortIndicatorRectForBounds:cellFrame];
[self.backgroundColor set];
CGRect res = NSMakeRect(frame.origin.x, cellFrame.origin.y, cellFrame.size.width - frame.origin.x, cellFrame.size.height);
NSRectFillUsingOperation(res , NSCompositingOperationSourceOver);
[NSColor.blueColor setFill];
[NSColor.blueColor setStroke];
[image drawInRect: [self sortIndicatorRectForBounds:frame]];
}
回答2:
Create the NSTableHeaderCell like below and play with the header cell's frame sizes and origins:
#import <Cocoa/Cocoa.h>
@interface CustomTableHeaderCell : NSTableHeaderCell {
NSMutableDictionary *attrs;
}
#import "CustomTableHeaderCell.h"
@implementation CustomTableHeaderCell
- (id)initTextCell:(NSString *)text
{
if (self = [super initTextCell:text]) {
if (text == nil || [text isEqualToString:@""]) {
[self setTitle:@"One"];
}
/* old_
self.textColor = [NSColor blackColor];
*/
// new_
self.textColor = [NSColor whiteColor];
attrs = [[NSMutableDictionary dictionaryWithDictionary:
[[self attributedStringValue]
attributesAtIndex:0
effectiveRange:NULL]]
mutableCopy];
// self.font = [NSFont fontWithName:appleeH8GhKr0 size:12];
return self;
}
return nil;
}
- (void)drawWithFrame:(NSRect)cellFrame
highlighted:(BOOL)isHighlighted
inView:(NSView *)view
{
CGRect fillRect, borderRect;
CGRectDivide(NSRectToCGRect(cellFrame), &borderRect, &fillRect, 1.0, CGRectMaxYEdge);
// sets the origin and frame for header's title
// fillRect.size.height = 25;
if (fillRect.origin.x == 0)
{
// fillRect.origin.y += 5;
// for adding left margin to first column title try to add spaces in text of header title
// try to play with the numbers of fillRect rect
}
else if (fillRect.origin.x == 239)
{
// fillRect.size.width -= 80;
}
// setting the background color of tableview's header view
NSGradient *gradient = [[NSGradient alloc]
initWithStartingColor:[NSColor greenColor]
endingColor:[NSColor greenColor]];
[gradient drawInRect:NSRectFromCGRect(fillRect) angle:90.0];
[self drawInteriorWithFrame:NSRectFromCGRect(CGRectInset(fillRect, 0.0, 1.0)) inView:view];
}
- (NSRect)adjustedFrameToVerticallyCenterText:(NSRect)frame
{
// super would normally draw text at the top of the cell
NSInteger offsetY = floor((NSHeight(frame) -
([[self font] ascender] - [[self font] descender])) / 2);
return NSInsetRect(frame, 20, offsetY);
}
-(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView{
[super drawInteriorWithFrame:[self adjustedFrameToVerticallyCenterText:cellFrame]
inView:controlView];
}
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)view
{
[self drawWithFrame:cellFrame highlighted:NO inView:view];
}
- (void)highlight:(BOOL)isHighlighted
withFrame:(NSRect)cellFrame
inView:(NSView *)view
{
[self drawWithFrame:cellFrame highlighted:isHighlighted inView:view];
}
Set the header cell for column like below:
NSArray *columns = nil;
if(self.tblVc)
columns = [self.tblVc tableColumns];
NSEnumerator *cols = [columns objectEnumerator];
NSTableColumn *col = nil;
CustomTableHeaderCell *headerCell;
while (col = [cols nextObject]) {
NSString *key = [[col headerCell] stringValue];
headerCell = [[CustomTableHeaderCell alloc]
initTextCell:key];
NSRect rectHeader =self.tblVc.headerView.frame;
rectHeader.size.height = 25;
self.tblVc.headerView.frame=rectHeader;
[col setHeaderCell:headerCell];
}
and use disclosure button on top of table view's header like below:
It will show the results like this:
回答3:
Thanks guys for your help. I've ended up with this simple solution which also solves the sort indicators. I've gave up on custom NSHeaderCell
and resolved everything in single custom NSTableHeaderRowView
method:
- (void)drawRect:(NSRect)dirtyRect
{
[NSGraphicsContext saveGraphicsState];
// Row: Fill the background
//
[NSColor.whiteColor setFill];
const CGRect headerRect = CGRectMake(0.0, 1.0, self.bounds.size.width, self.bounds.size.height-1.0);
[[NSBezierPath bezierPathWithRect:headerRect] fill];
// Columns
//
for (NSUInteger i = 0; i < self.tableView.numberOfColumns; ++i) {
NSRect rect = [self headerRectOfColumn:i];
// separator on left
//
if (i != 0) {
[[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.35] setFill];
[[NSBezierPath bezierPathWithRect:NSMakeRect(rect.origin.x, rect.origin.y+4.0, 1.0, rect.size.height-8.0)] fill];
}
NSTableColumn *tableColumn = self.tableView.tableColumns[i];
NSSortDescriptor *col_sd = tableColumn.sortDescriptorPrototype;
NSTableHeaderCell *tableHeaderCell = tableColumn.headerCell;
// text
//
NSString *columnText = tableHeaderCell.stringValue;
[columnText drawInRect:NSInsetRect(rect, 5.0, 4.0) withAttributes:nil];
// sort indicator
//
for (NSInteger priority = 0; priority < self.tableView.sortDescriptors.count; ++priority) {
NSSortDescriptor *sortDesciptor = self.tableView.sortDescriptors[priority];
// <C> there is no way to get column from sortDesciptor so I use this trick comparing sortDesciptor with current column.sortDescriptorPrototype;
// <C> not sure why sel_isEqual() dosn't work so I compare its string representation;
if ([NSStringFromSelector(sortDesciptor.selector) isEqualToString:NSStringFromSelector(col_sd.selector)] == YES && [sortDesciptor.key isEqualToString:col_sd.key] == YES) {
SDEBUG(LEVEL_DEBUG, @"sort-hdr", @"MATCH: sel=%@", NSStringFromSelector(sortDesciptor.selector));
// <C> default implementation draws indicator ONLY for priority 0; Otherwise the indicator would have to graphically show the prio;
// <C> it is a support for multi-column sorting;
// <REF> shall you need it: https://stackoverflow.com/questions/46611972/sorting-a-viewbased-tableview-with-multiple-sort-descriptors-doesnt-work
[tableHeaderCell drawSortIndicatorWithFrame:rect inView:self ascending:sortDesciptor.ascending priority:priority];
}
}
}
[NSGraphicsContext restoreGraphicsState];
}
来源:https://stackoverflow.com/questions/52315708/how-to-change-nstableview-header-background-color-in-mac-os-x-app