at_yasu's blog

ロード的なことを

NSView on NSCell

できたのでメモ書き



  • あらかじめ、InterfaceBuilderで表示させるTableの横幅と、表示させる中身の横幅を決めておく。なお、表示させる中身とTableは別々のNibファイルにしておく事。
  • NSCellを継承して、NSViewを保持するような仕組みを持たせる
  • NSTableColumnに、NSCellを継承させたオブジェクトを保持させる
  • NSTableViewのdataSource先では、「- (int) numberOfRowsInTableView:」「- (void) tableView:willDisplayCell:forTableColumn:row:」を実装させる。
  • 「- (void) tableView:willDisplayCell:forTableColumn:row:」の内部では、第二引数のNSCellが継承したオブジェクトなはずなので、「cell setView:」と言った感じでViewを入れる
  • NSCellの継承したオブジェクトで「drawWithFrame:inView:」をオーバーライドし、第一引数のNSRect型を保持しているNSViewのsetFrameで指定させ、第二引数のNSViewのメソッド「addSubview:」で保持しているNSViewを追加させる。


注意点

  • NSTableViewの更新が、パフォーマンスを理由に全てのCellを見に行かないらしい。つまり、[nstableview display]とやってもcellの中身までは更新しないっぽい。解決として、下記のような力技更新が必要みたい。ただ、明らかにパフォーマンスが遅くなるので注意。
  • CocoaBindingしか使ってなかったら泣きの涙。*1


以下、部分的なソース。不要っぽそうな部分は消しているから、所々よくわからないかも。


NSTableViewの解決策っぽい物

- (void) reloadTableView
{
    while ([[self subviews] count] > 0)
    {
		[[[self subviews] lastObject] removeFromSuperviewWithoutNeedingDisplay];
    }
    
    [self reloadData];
}


NSTableViewのDataSource部分

// =============================================================================
#pragma mark NSTableView's datasouce methods.
#pragma mark 

//
// - (NSMutableArray*) movieControllers;
// 表示するデータを保存し蓄えている。中は、NCDownloadDataというオブジェクト。
//

- (int) numberOfRowsInTableView:(NSTableView *) tableView
{
    return [[self movieControllers] count];
}

- (void) tableView:(NSTableView *) tableView
   willDisplayCell:(id) cell
    forTableColumn:(NSTableColumn *) tableColumn
               row:(int) row
{
	[(NCDownloadCell *)cell setNCDownloadData:(NCDownloadData*)[[self movieControllers] objectAtIndex: row]];
}

NSCellを継承したオブジェクト

@interface NCDownloadCell : NSCell {

@private
	NCDownloadData* __ncdc_data;
	id _delegate;
	NSView* _ncdc_view;
}

- (void) setNCDownloadData:(NCDownloadData*)data;
- (NCDownloadData*) downloadData;
- (void) setDelegate:(id)dele;
- (id) delegate;
@end

@implementation NCDownloadCell

- (id)init {
    self = [super init];
    if (self) {
        // Initialization code here.
	__ncdc_data = nil;
	_ncdc_view = nil;
    }
    return self;
}

- (void) dealloc
{
	if (__ncdc_data) [__ncdc_data release], __ncdc_data = nil;
	if (_ncdc_view)  [_ncdc_view release],  _ncdc_view = nil;
	[super dealloc];
}

// =============================================================================
// Cellの内容を表示させる
- (void)drawWithFrame:(NSRect)cellFrame 
               inView:(NSView *)controlView
{
	[super drawWithFrame:cellFrame
		      inView:controlView];
	
	NSView* v = [__ncdc_data view];
	[v setFrame:cellFrame];
	
	if ([v superview] != controlView)
	{
		_ncdc_view = v;
		[controlView addSubview:_ncdc_view];
	}
	MyAssert1(@"drawWithFrame: ncdc_data: %@", __ncdc_data);
	MyAssert1(@"drawWithFrame: _ncdc_view: %@", _ncdc_view);
}

// =============================================================================
- (void) setNCDownloadData:(NCDownloadData*)data
{
	@synchronized (__ncdc_data)
	{
		if (__ncdc_data == nil || __ncdc_data != data)
		{
			if (__ncdc_data) [__ncdc_data release], __ncdc_data = nil;
			__ncdc_data = [data retain];
			[__ncdc_data setDelegate:self];
		}
	}
}

- (NCDownloadData*) downloadData
{
	return __ncdc_data;
}

// =============================================================================

- (void) setDelegate:(id)dele
{
	@synchronized (_delegate)
	{
		if (_delegate != dele)
			_delegate = [dele retain];
	}
}

- (id) delegate
{
	return _delegate;
}

@end


NCDownloadData(データそのもの)
なおNCDownloadDataは、downloadContentsView.nibではfilesOwnerになっている。
NCDownloadDataViewはNSViewの継承クラス。

@interface NCDownloadData : NSWindowController <NCMovieDownloadStatePassing> {
	IBOutlet NCDownloadDataView*  _ncdd_viewer;
	
@private
	double               _nd_counter;
	double               _nd_len;
	id                   _nd_delegate;
}

- (void) setDelegate:(id)obj;
- (id) delegate;

- (NSView*) view;

// some methods...

@end

@implementation NCDownloadData
- (id) init
{
//	self = [super initWithWindowNibName:@"downloadContentsView"]; // I don't know why not run this.
	self = [super init];
	if (!self)
		return nil;
	
	// 表示させる中身のNibファイルの読み込み。
	if (![NSBundle loadNibNamed:@"downloadContentsView" owner: self])
	{
		MyAssert(@"init Error !!");
		[self release];
		return nil;
	}
	
	MyAssert(@"Init");
	
	_nd_len = 0.0;
	_nd_counter = 0.0;
	_nd_delegate = nil;
	
	return self;
}

- (void) dealloc
{
	[super dealloc];
}

- (NSView*) view
{
	return _ncdd_viewer;
}

// has a meny methods ...
@end

*1:つか、何か良い方法ないかなー。CocoaBindingを自作できるほど技量ねぇし…