0

I have an NSScrollView which is set to be layer backed by clicking the layer checkmark on the scrollview in IB. Within that scrollview I have an NSTableView. In that NSTableView I use a custom NSTableRowView to draw a vertical red line at the divider between my columns. My NSTableView has 2 columns. With my scrollview set to be layer backed, whenever I drag the column divider to make the column wider or narrower, drawBackgroundInRect is not being called, so my background vertical line does not get updated during the drag operation.

Is there a workaround for this?

Here's the code I'm using in drawBackgroundInRect:

- (void)drawBackgroundInRect:(NSRect)dirtyRect {

[super drawBackgroundInRect:dirtyRect];

NSGraphicsContext *nscontext = [NSGraphicsContext currentContext];

[nscontext saveGraphicsState];

CGContextRef context = (CGContextRef)[nscontext graphicsPort];

if (!self.isGroupRowStyle) {

    CGRect leftColumnRect = [(NSView *)[self viewAtColumn:0] frame];
    leftColumnRect.origin.y -= 1.0;
    leftColumnRect.size.height += 2.0;
    leftColumnRect.size.width += 1.0;

    // grey background

    CGContextSetGrayFillColor(context, 0.98, 1.0);
    CGContextFillRect(context, leftColumnRect);

    // draw nice red vertical line

    CGContextBeginPath(context);
    CGContextSetRGBStrokeColor(context, 0.6, 0.2, 0.2, 0.3);

    CGContextMoveToPoint(context, leftColumnRect.size.width + 1.5, 0);
    CGContextSetLineWidth(context, 1.0);
    CGContextAddLineToPoint(context, leftColumnRect.size.width + 1.5, leftColumnRect.size.height);
    CGContextStrokePath(context);

}
[nscontext restoreGraphicsState];
}

Here's the problem that I'm getting when resizing the column:

enter image description here

And this is what it should look like when I don't have my scrollview set to be layer backed:

enter image description here

Thanks!

Tap Forms
  • 912
  • 8
  • 16

1 Answers1

1

After more than a year, I finally figured out a proper answer to my own question.

The trick is to call setNeedsDisplay on every row in the list of visible rows as the NSTableColumn is being resized.

Here's the code I use in my NSViewController subclass:

- (void)tableView:(NSTableView *)tableView isResizingTableColumn:(NSTableColumn *)tableColumn {
    NSRange range = [tableView rowsInRect:tableView.visibleRect];
    for (NSInteger row = range.location; row < (range.location + range.length); row++) {
        NSTableRowView *rowView = [tableView rowViewAtRow:row makeIfNecessary:NO];
        [rowView setNeedsDisplay:YES];
    }
}

My NSTableView has as its header view my own subclass of NSTableHeaderView. I set my NSViewController as the delegate for the header view so I can track when the user resizes.

TFRecordTableHeaderView.h:

#import <Cocoa/Cocoa.h>

@protocol TFRecordTableHeaderViewDelegate <NSObject>
- (void)tableView:(NSTableView *)tableView isResizingTableColumn:(NSTableColumn *)tableColumn;
@end

@interface TFRecordTableHeaderView : NSTableHeaderView
@property (nonatomic, assign) id<TFRecordTableHeaderViewDelegate>delegate;
@end

TFRecordTableHeaderView.m:

#import "TFRecordTableHeaderView.h"

@implementation TFRecordTableHeaderView

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];

    if (self) {
        // Initialization code here.
    }

    return self;
}

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];
    // do some custom drawing here
}

// track resizing of the column as it happens
// from the docs: If the user is resizing a column in the receiver, returns the index of that column.

- (NSInteger)resizedColumn {
    NSInteger columnIndex = [super resizedColumn];

    if (columnIndex >= 0) {
        NSTableColumn *column = [self.tableView.tableColumns objectAtIndex:columnIndex];

        if ([self.delegate respondsToSelector:@selector(tableView:isResizingTableColumn:)]) {
            [self.delegate tableView:self.tableView isResizingTableColumn:column];
        }
    }

    return columnIndex;
}

@end

Just set the NSTableHeaderView subclass to your NSTableView's header view. Now whenever you drag a column in the header, the isResizingTableColumn delegate method will be called. You can implement the isResizingTableColumn on your NSViewController subclass.

Now when you resize a column, isResizingTableColumn will be called which will get the NSTableRowViews for the visible rect and it will send setNeedsDisplay. That will cause the rows to be refreshed and the drawBackgroundInRect method to get called while dragging. This in turn will refresh my custom vertical grid lines.

All this to avoid drawing a vertical grid line over top grouped rows on an NSTableView. I think this should be built-in to NSTableView. It looks really bad if your group section headers have text in them and there's a vertical grid line running right over top the text.

Now that I think about it, you could just do this right on your NSTableHeaderView subclass:

- (NSInteger)resizedColumn {
    NSInteger columnIndex = [super resizedColumn];

    if (columnIndex >= 0) {         
        NSRange range = [self.tableView rowsInRect:self.tableView.visibleRect];
        for (NSInteger row = range.location; row < (range.location + range.length); row++) {
            NSTableRowView *rowView = [self.tableView rowViewAtRow:row makeIfNecessary:NO];
            [rowView setNeedsDisplay:YES];
        }
    }

    return columnIndex;
}

In my case I was doing it on my NSViewController subclass because while I resized one table column I was also resizing another equally. This was simulating a footer row with another similarly configured table that had only one row in it since NSTableView doesn't have the concept of a footer like UITableView does.

Tap Forms
  • 912
  • 8
  • 16