Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

misee

macrumors member
Original poster
I have a custom cell for a NSTableView. My cell is a subclass of NSActionCell and contains three sub-cells, each a NSTextFieldCell. Two are at the top of my cell, placed next to each other and contain only one line of text, the third is below them and takes up the rest of the cells space (there can be multiple lines). Now I have two things I can't get to work:
  1. All of the sub-cells should be editable, however I couldn't find a way to do so.
  2. I would like to be able to adjust the row height, so I somehow need to figure out the height that the third cell needs to display it's text.

I would be glad if you could point me to the right direction to accomplish these things.

Here's the code for my custom cell:
Code:
//
//  SESubtitleCell.h
//  Custom Table Cell
//

#import <Cocoa/Cocoa.h>


@interface SESubtitleCell : NSActionCell
{
@private
	NSTextFieldCell *startTimeCell;
	NSTextFieldCell *endTimeCell;
	NSTextFieldCell *subtitleTextCell;
}

@property (retain) NSString *startTime;
@property (retain) NSString *endTime;
@property (retain) NSString *subtitleText;

@end
Code:
//
//  SESubtitleCell.m
//  Custom Table Cell
//

#import "SESubtitleCell.h"

static const CGFloat _SESubtitleCellSubtitleTextLeftInset = 4.0;
static const CGFloat _SESubtitleCellSubtitleTextTopInset = 5.0;
static const CGFloat _SESubtitleCellTimeTopInset = 3.0;
static const CGFloat _SESubtitleCellTimeHeight = 15.0;
static const CGFloat _SESubtitleCellTimeWidth = 80.0;
static const CGFloat _SESubtitleCellStartTimeInset = 4.0;
static const CGFloat _SESubtitleCellEndTimeInset = 7.0;


@interface SESubtitleCell (private)

- (NSRect)_subtitleFrameForInteriorFrame:(NSRect)frame;
- (NSRect)_startTimeFrameForInteriorFrame:(NSRect)frame;
- (NSRect)_endTimeFrameForInteriorFrame:(NSRect)frame;

- (void)_ensureAllCellsCreated;
- (void)_ensureStartTimeCellCreated;
- (void)_ensureEndTimeCellCreated;
- (void)_ensureSubtitleTextCellCreated;

@end



@implementation SESubtitleCell

- (id)copyWithZone:(NSZone *)zone
{
	SESubtitleCell *result = [super copyWithZone:zone];
	if (result == nil)
		return nil;
	
	result->startTimeCell = [startTimeCell copyWithZone:zone];
	result->endTimeCell = [endTimeCell copyWithZone:zone];
	result->subtitleTextCell = [subtitleTextCell copyWithZone:zone];
	
	return result;
}

- (void)dealloc
{
	[startTimeCell release];
	[endTimeCell release];
	
	[super dealloc];
}

// MARK: -
// MARK: Getters and Setters

@dynamic startTime;
@dynamic endTime;
@dynamic subtitleText;

- (NSString *)startTime
{
	return [startTimeCell title];
}

- (void)setStartTime:(NSString *)newStartTime
{
	[self _ensureStartTimeCellCreated];
	
	[startTimeCell setTitle:newStartTime];
}

- (NSString *)endTime
{
	return [endTimeCell title];
}

- (void)setEndTime:(NSString *)newEndTime
{
	[self _ensureEndTimeCellCreated];
	
	[endTimeCell setTitle:newEndTime];
}

- (NSString *)subtitleText
{
	return [subtitleTextCell title];
}

- (void)setSubtitleText:(NSString *)newSubtitleText
{
	[self _ensureSubtitleTextCellCreated];
	
	[subtitleTextCell setTitle:newSubtitleText];
}

// MARK: -
// MARK: Override some setters that must be set for the sub-cells too

- (void)setControlView:(NSView *)controlView
{
	[super setControlView:controlView];
	
	[self _ensureAllCellsCreated];
	
	[startTimeCell setControlView:controlView];
	[endTimeCell setControlView:controlView];
	[subtitleTextCell setControlView:controlView];
}

- (void)setBackgroundStyle:(NSBackgroundStyle)style
{
	[super setBackgroundStyle:style];
	
	[self _ensureAllCellsCreated];
	
	[startTimeCell setBackgroundStyle:style];
	[endTimeCell setBackgroundStyle:style];
	[subtitleTextCell setBackgroundStyle:style];
}

// MARK: -
// MARK: Calculate the frames for drawing

- (NSRect)_subtitleFrameForInteriorFrame:(NSRect)frame
{
	NSRect result = frame;
	
	result.origin.x = _SESubtitleCellSubtitleTextLeftInset;
	result.size.width = NSMaxX(frame) - NSMinX(result);
	
	result.origin.y = NSMaxY([self _startTimeFrameForInteriorFrame:frame]) + _SESubtitleCellSubtitleTextTopInset;
	result.size.height = NSMaxY(frame) - NSMinY(result);
	
	return result;
}

- (NSRect)_startTimeFrameForInteriorFrame:(NSRect)frame
{
	NSRect result = frame;
	
	result.origin.x = _SESubtitleCellStartTimeInset;
	result.size.width = _SESubtitleCellTimeWidth;
	
	result.origin.y = NSMinY(frame) + _SESubtitleCellTimeTopInset;
	result.size.height = _SESubtitleCellTimeHeight;
	
	return result;
}

- (NSRect)_endTimeFrameForInteriorFrame:(NSRect)frame
{
	NSRect starTimeFrame = [self _startTimeFrameForInteriorFrame:frame];
	NSRect result = starTimeFrame;
	
	result.origin.x = NSMaxX(starTimeFrame) + _SESubtitleCellEndTimeInset;
	
	return result;
}

// MARK: Draw

- (void)drawInteriorWithFrame:(NSRect)frame inView:(NSView *)controlView
{
	[self _ensureAllCellsCreated];
	
	NSRect startTimeFrame = [self _startTimeFrameForInteriorFrame:frame];
	[startTimeCell drawWithFrame:startTimeFrame inView:controlView];
	
	NSRect endTimeFrame = [self _endTimeFrameForInteriorFrame:frame];
	[endTimeCell drawWithFrame:endTimeFrame inView:controlView];
	
	NSRect titleFrame = [self _subtitleFrameForInteriorFrame:frame];
	[subtitleTextCell drawInteriorWithFrame:titleFrame inView:controlView];
}

// MARK: -
// MARK: Hit Testing

- (NSUInteger)hitTestForEvent:(NSEvent *)event inRect:(NSRect)frame ofView:(NSView *)controlView
{
	[self _ensureAllCellsCreated];
	
	NSPoint point = [controlView convertPoint:[event locationInWindow] fromView:nil];
	
	NSRect startTimeFrame = [self _startTimeFrameForInteriorFrame:frame];
	if (NSPointInRect(point, startTimeFrame))
		return [startTimeCell hitTestForEvent:event inRect:startTimeFrame ofView:controlView];
	
	NSRect endTimeFrame = [self _endTimeFrameForInteriorFrame:frame];
	if (NSPointInRect(point, endTimeFrame))
		return [endTimeCell hitTestForEvent:event inRect:endTimeFrame ofView:controlView];
	
	NSRect subtitleFrame = [self _subtitleFrameForInteriorFrame:frame];
	if (NSPointInRect(point, subtitleFrame))
		return [subtitleTextCell hitTestForEvent:event inRect:subtitleFrame ofView:controlView];
	
	return NSCellHitNone;
}

// MARK: -
// MARK: Set Object Value

- (void)setObjectValue:(id <NSCopying>)obj
{
	[super setObjectValue:obj];
	
	[self _ensureAllCellsCreated];
	
	if (obj) {
		[startTimeCell bind:@"title" toObject:[self objectValue] withKeyPath:@"startTime" options:nil];
		[endTimeCell bind:@"title" toObject:[self objectValue] withKeyPath:@"endTime" options:nil];
		[subtitleTextCell bind:@"title" toObject:[self objectValue] withKeyPath:@"subtitle" options:nil];
	}
}

// MARK: -
// MARK: Create the sub-cells

- (void)_ensureAllCellsCreated
{
	[self _ensureStartTimeCellCreated];
	[self _ensureEndTimeCellCreated];
	[self _ensureSubtitleTextCellCreated];
}

- (void)_ensureStartTimeCellCreated
{
	if (startTimeCell == nil) {
		startTimeCell = [[NSTextFieldCell alloc] init];
		[startTimeCell setControlView:self.controlView];
		[startTimeCell setBackgroundStyle:self.backgroundStyle];
		[startTimeCell setTextColor:[NSColor grayColor]];
	}
}

- (void)_ensureEndTimeCellCreated
{
	if (endTimeCell == nil) {
		endTimeCell = [[NSTextFieldCell alloc] init];
		[endTimeCell setControlView:self.controlView];
		[endTimeCell setBackgroundStyle:self.backgroundStyle];
		[endTimeCell setTextColor:[NSColor grayColor]];
	}
}

- (void)_ensureSubtitleTextCellCreated
{
	if (subtitleTextCell == nil) {
		subtitleTextCell = [[NSTextFieldCell alloc] init];
		[subtitleTextCell setControlView:self.controlView];
		[subtitleTextCell setBackgroundStyle:self.backgroundStyle];
	}
}

@end
 
Look at the NSCell docs under the Editing and Selecting Text section. There are several methods you'd need to override in your base cell and forward to your subcells.
 
Thanks for the response. Unfortunately, I'm still not quite there yet... I over rid the editWithFrame:inView:editor:delegate:event:, but it is never called. Same goes for the selectWithFrame:inView:editor:delegate:start:length: method. The hit test returns 3, which I believe means it's an editable content area (so it would be the correct value I guess). You wouldn't happen to know of any sample code that does something similar to what I'm trying to get an idea? I've looked at AnimatedTableView, but they use an NSTextFieldCell. I'm using an array controller to set my table contents, so that wouldn't really work for me except I abandon the bindings and go for delegation.

EDIT:
I got most of the stuff to work now. I had to make my cell a subclass of NSTextFieldCell. I encountered some problems, but I guess I will be able to solve these by moving away from NSArrayController for populating my table and use a data source and delegate to set the actual represented object.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.